use core::convert::{TryFrom, TryInto};
use core::fmt;
use core::ops::{
Add, AddAssign, Bound, Range, RangeBounds, RangeInclusive, RangeTo, RangeToInclusive, Sub,
SubAssign,
};
use std::borrow::Cow;
use shorthand::ShortHand;
use crate::Error;
#[derive(ShortHand, Copy, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
#[shorthand(enable(must_use, copy), disable(option_as_ref, set))]
pub struct ByteRange {
start: Option<usize>,
end: usize,
}
impl ByteRange {
pub fn set_len(&mut self, new_len: usize) {
if let Some(value) = self.len().checked_sub(new_len) {
self.end -= value;
} else {
self.end += new_len.saturating_sub(self.len());
}
}
pub fn set_start(&mut self, new_start: Option<usize>) -> &mut Self {
if new_start.map_or(false, |s| s > self.end) {
panic!(
"attempt to make the start ({}) larger than the end ({})",
new_start.unwrap(),
self.end
);
}
self.start = new_start;
self
}
#[must_use]
pub fn saturating_add(mut self, num: usize) -> Self {
if let Some(start) = self.start {
if let (Some(start), Some(end)) = (start.checked_add(num), self.end.checked_add(num)) {
self.start = Some(start);
self.end = end;
} else {
if let Some(start) = start.checked_add(usize::max_value() - self.end) {
self.start = Some(start);
self.end = usize::max_value();
} else {
}
}
} else {
self.end = self.end.saturating_add(num);
}
self
}
#[must_use]
pub fn saturating_sub(mut self, num: usize) -> Self {
if let Some(start) = self.start {
if let (Some(start), Some(end)) = (start.checked_sub(num), self.end.checked_sub(num)) {
self.start = Some(start);
self.end = end;
} else {
if let Some(end) = self.end.checked_sub(start) {
self.start = Some(0);
self.end = end;
} else {
}
}
} else {
self.end = self.end.saturating_sub(num);
}
self
}
#[inline]
#[must_use]
pub fn len(&self) -> usize { self.end.saturating_sub(self.start.unwrap_or(0)) }
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool { self.len() == 0 }
}
impl Sub<usize> for ByteRange {
type Output = Self;
#[must_use]
#[inline]
fn sub(self, rhs: usize) -> Self::Output {
Self {
start: self.start.map(|lhs| lhs - rhs),
end: self.end - rhs,
}
}
}
impl SubAssign<usize> for ByteRange {
#[inline]
fn sub_assign(&mut self, other: usize) { *self = <Self as Sub<usize>>::sub(*self, other); }
}
impl Add<usize> for ByteRange {
type Output = Self;
#[must_use]
#[inline]
fn add(self, rhs: usize) -> Self::Output {
Self {
start: self.start.map(|lhs| lhs + rhs),
end: self.end + rhs,
}
}
}
impl AddAssign<usize> for ByteRange {
#[inline]
fn add_assign(&mut self, other: usize) { *self = <Self as Add<usize>>::add(*self, other); }
}
macro_rules! impl_from_ranges {
( $( $type:tt ),* ) => {
$(
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
impl From<Range<$type>> for ByteRange {
fn from(range: Range<$type>) -> Self {
if range.start > range.end {
panic!(
"the range start ({}) must be smaller than the end ({})",
range.start, range.end
);
}
Self {
start: Some(range.start as usize),
end: range.end as usize,
}
}
}
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
impl From<RangeInclusive<$type>> for ByteRange {
fn from(range: RangeInclusive<$type>) -> Self {
let (start, end) = range.into_inner();
if start > end {
panic!(
"the range start ({}) must be smaller than the end ({}+1)",
start, end
);
}
Self {
start: Some(start as usize),
end: (end as usize).saturating_add(1),
}
}
}
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
impl From<RangeTo<$type>> for ByteRange {
fn from(range: RangeTo<$type>) -> Self {
Self {
start: None,
end: range.end as usize,
}
}
}
#[allow(trivial_numeric_casts, clippy::fallible_impl_from)]
impl From<RangeToInclusive<$type>> for ByteRange {
fn from(range: RangeToInclusive<$type>) -> Self {
Self {
start: None,
end: (range.end as usize).saturating_add(1),
}
}
}
)*
}
}
impl_from_ranges![u64, u32, u16, u8, usize, i32];
#[must_use]
impl RangeBounds<usize> for ByteRange {
fn start_bound(&self) -> Bound<&usize> {
if let Some(start) = &self.start {
Bound::Included(start)
} else {
Bound::Unbounded
}
}
#[inline]
fn end_bound(&self) -> Bound<&usize> { Bound::Excluded(&self.end) }
}
impl TryInto<RangeTo<usize>> for ByteRange {
type Error = Error;
fn try_into(self) -> Result<RangeTo<usize>, Self::Error> {
if self.start.is_some() {
return Err(Error::custom("a `RangeTo` (`..end`) does not have a start"));
}
Ok(RangeTo { end: self.end })
}
}
impl TryInto<Range<usize>> for ByteRange {
type Error = Error;
fn try_into(self) -> Result<Range<usize>, Self::Error> {
if self.start.is_none() {
return Err(Error::custom(
"a `Range` (`start..end`) has to have a start.",
));
}
Ok(Range {
start: self.start.unwrap(),
end: self.end,
})
}
}
impl fmt::Display for ByteRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.len())?;
if let Some(value) = self.start {
write!(f, "@{}", value)?;
}
Ok(())
}
}
impl TryFrom<&str> for ByteRange {
type Error = Error;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let mut input = input.splitn(2, '@');
let length = input.next().unwrap();
let length = length
.parse::<usize>()
.map_err(|e| Error::parse_int(length, e))?;
let start = input
.next()
.map(|v| v.parse::<usize>().map_err(|e| Error::parse_int(v, e)))
.transpose()?;
Ok(Self {
start,
end: start.unwrap_or(0) + length,
})
}
}
impl<'a> TryFrom<Cow<'a, str>> for ByteRange {
type Error = Error;
fn try_from(input: Cow<'a, str>) -> Result<Self, Self::Error> {
Self::try_from(input.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
#[should_panic = "the range start (6) must be smaller than the end (0)"]
#[allow(clippy::reversed_empty_ranges)]
fn test_from_range_panic() { let _ = ByteRange::from(6..0); }
#[test]
#[should_panic = "the range start (6) must be smaller than the end (0+1)"]
#[allow(clippy::reversed_empty_ranges)]
fn test_from_range_inclusive_panic() { let _ = ByteRange::from(6..=0); }
#[test]
fn test_from_ranges() {
assert_eq!(ByteRange::from(1..10), ByteRange::from(1..=9));
assert_eq!(ByteRange::from(..10), ByteRange::from(..=9));
}
#[test]
fn test_range_bounds() {
assert_eq!(ByteRange::from(0..10).start_bound(), Bound::Included(&0));
assert_eq!(ByteRange::from(..10).start_bound(), Bound::Unbounded);
assert_eq!(ByteRange::from(0..10).end_bound(), Bound::Excluded(&10));
assert_eq!(ByteRange::from(..10).end_bound(), Bound::Excluded(&10));
}
#[test]
fn test_try_into() {
assert_eq!(ByteRange::from(1..4).try_into(), Ok(1..4));
assert_eq!(ByteRange::from(..4).try_into(), Ok(..4));
assert!(TryInto::<RangeTo<usize>>::try_into(ByteRange::from(1..4)).is_err());
assert!(TryInto::<Range<usize>>::try_into(ByteRange::from(..4)).is_err());
}
#[test]
fn test_add_assign() {
let mut range = ByteRange::from(5..10);
range += 5;
assert_eq!(range, ByteRange::from(10..15));
}
#[test]
#[should_panic = "attempt to add with overflow"]
fn test_add_assign_panic() {
let mut range = ByteRange::from(4..usize::max_value());
range += 5;
unreachable!();
}
#[test]
fn test_sub_assign() {
let mut range = ByteRange::from(10..20);
range -= 5;
assert_eq!(range, ByteRange::from(5..15));
}
#[test]
#[should_panic = "attempt to subtract with overflow"]
fn test_sub_assign_panic() {
let mut range = ByteRange::from(4..10);
range -= 5;
unreachable!();
}
#[test]
#[should_panic = "attempt to make the start (11) larger than the end (10)"]
fn test_set_start() { let _ = ByteRange::from(4..10).set_start(Some(11)); }
#[test]
#[allow(clippy::identity_op)]
fn test_add() {
assert_eq!(ByteRange::from(5..10) + 5, ByteRange::from(10..15));
assert_eq!(ByteRange::from(..10) + 5, ByteRange::from(..15));
assert_eq!(ByteRange::from(5..10) + 0, ByteRange::from(5..10));
assert_eq!(ByteRange::from(..10) + 0, ByteRange::from(..10));
}
#[test]
#[should_panic = "attempt to add with overflow"]
fn test_add_panic() { let _ = ByteRange::from(usize::max_value()..usize::max_value()) + 1; }
#[test]
#[allow(clippy::identity_op)]
fn test_sub() {
assert_eq!(ByteRange::from(5..10) - 4, ByteRange::from(1..6));
assert_eq!(ByteRange::from(..10) - 4, ByteRange::from(..6));
assert_eq!(ByteRange::from(0..0) - 0, ByteRange::from(0..0));
assert_eq!(ByteRange::from(2..3) - 0, ByteRange::from(2..3));
assert_eq!(ByteRange::from(..0) - 0, ByteRange::from(..0));
assert_eq!(ByteRange::from(..3) - 0, ByteRange::from(..3));
}
#[test]
#[should_panic = "attempt to subtract with overflow"]
fn test_sub_panic() { let _ = ByteRange::from(0..0) - 1; }
#[test]
fn test_saturating_add() {
assert_eq!(
ByteRange::from(5..10).saturating_add(5),
ByteRange::from(10..15)
);
assert_eq!(
ByteRange::from(..10).saturating_add(5),
ByteRange::from(..15)
);
assert_eq!(
ByteRange::from(6..11).saturating_add(0),
ByteRange::from(6..11)
);
assert_eq!(
ByteRange::from(..11).saturating_add(0),
ByteRange::from(..11)
);
assert_eq!(
ByteRange::from(0..0).saturating_add(0),
ByteRange::from(0..0)
);
assert_eq!(ByteRange::from(..0).saturating_add(0), ByteRange::from(..0));
assert_eq!(
ByteRange::from(usize::max_value()..usize::max_value()).saturating_add(1),
ByteRange::from(usize::max_value()..usize::max_value())
);
assert_eq!(
ByteRange::from(..usize::max_value()).saturating_add(1),
ByteRange::from(..usize::max_value())
);
assert_eq!(
ByteRange::from(usize::max_value() - 5..usize::max_value()).saturating_add(1),
ByteRange::from(usize::max_value() - 5..usize::max_value())
);
assert_eq!(
ByteRange::from(usize::max_value() - 5..usize::max_value() - 3).saturating_add(4),
ByteRange::from(usize::max_value() - 2..usize::max_value())
);
assert_eq!(
ByteRange::from(..usize::max_value() - 3).saturating_add(4),
ByteRange::from(..usize::max_value())
);
}
#[test]
fn test_saturating_sub() {
assert_eq!(
ByteRange::from(5..10).saturating_sub(4),
ByteRange::from(1..6)
);
assert_eq!(
ByteRange::from(0..0).saturating_sub(0),
ByteRange::from(0..0)
);
assert_eq!(
ByteRange::from(2..3).saturating_sub(0),
ByteRange::from(2..3)
);
assert_eq!(
ByteRange::from(0..5).saturating_sub(4),
ByteRange::from(0..5)
);
assert_eq!(
ByteRange::from(1..5).saturating_sub(2),
ByteRange::from(0..4)
);
assert_eq!(
ByteRange::from(1..3).saturating_sub(5),
ByteRange::from(0..2)
);
assert_eq!(
ByteRange::from(0..0).saturating_sub(1),
ByteRange::from(0..0)
);
assert_eq!(ByteRange::from(..6).saturating_sub(2), ByteRange::from(..4));
assert_eq!(ByteRange::from(..5).saturating_sub(0), ByteRange::from(..5));
assert_eq!(ByteRange::from(..0).saturating_sub(0), ByteRange::from(..0));
assert_eq!(ByteRange::from(..0).saturating_sub(1), ByteRange::from(..0));
}
#[test]
fn test_display() {
assert_eq!(ByteRange::from(0..5).to_string(), "5@0".to_string());
assert_eq!(
ByteRange::from(2..100_001).to_string(),
"99999@2".to_string()
);
assert_eq!(ByteRange::from(..99999).to_string(), "99999".to_string());
}
#[test]
fn test_parser() {
assert_eq!(ByteRange::from(2..22), ByteRange::try_from("20@2").unwrap());
assert_eq!(ByteRange::from(..300), ByteRange::try_from("300").unwrap());
assert_eq!(
ByteRange::try_from("a"),
Err(Error::parse_int("a", "a".parse::<usize>().unwrap_err()))
);
assert_eq!(
ByteRange::try_from("1@a"),
Err(Error::parse_int("a", "a".parse::<usize>().unwrap_err()))
);
assert!(ByteRange::try_from("").is_err());
}
}