-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement support for
Size::from_str()
Supports any mix-and-match of the following text formats: * 1234 * 1234 b/kb/mb/etc * 1234 B/KB/MB/etc * 1234 B/KiB/MiB/etc * 1234MB * 12.34 GB * 1234 byte/kilobyte/terabyte/etc * 1234 bytes/kilobytes/terabytes/etc * 12.34 Kibibytes/MegaBytes/etc Supporting tests added.
- Loading branch information
Showing
2 changed files
with
164 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
use std::error::Error; | ||
use std::str::FromStr; | ||
|
||
use crate::consts::*; | ||
use crate::Size; | ||
|
||
/// Represents an error parsing a `Size` from a string representation. | ||
#[derive(Debug, PartialEq, Clone, Eq)] | ||
pub struct ParseSizeError; | ||
|
||
impl Error for ParseSizeError {} | ||
impl core::fmt::Display for ParseSizeError { | ||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
f.write_str("Error parsing Size") | ||
} | ||
} | ||
|
||
impl Size { | ||
/// Parse a string representation of size to a `Size` value. | ||
/// | ||
/// Supports any mix-and-match of the following text formats: | ||
/// * 1234 | ||
/// * 1234 b/kb/mb/etc | ||
/// * 1234 B/KB/MB/etc | ||
/// * 1234 B/KiB/MiB/etc | ||
/// * 1234MB | ||
/// * 12.34 GB | ||
/// * 1234 byte/kilobyte/terabyte/etc | ||
/// * 1234 bytes/kilobytes/terabytes/etc | ||
/// * 12.34 Kibibytes/MegaBytes/etc | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// use size::Size; | ||
/// | ||
/// let size = Size::from_str("12.34 KB").unwrap(); | ||
/// assert_eq!(size.bytes(), 12_340); | ||
/// ``` | ||
pub fn from_str(s: &str) -> Result<Size, crate::ParseSizeError> { | ||
FromStr::from_str(s) | ||
} | ||
} | ||
|
||
/// This test just ensures everything is wired up correctly between the member function | ||
/// `[Size::from_str()]` and the `FromStr` trait impl. | ||
#[test] | ||
fn from_str() { | ||
let input = "12.34 kIloByte"; | ||
let parsed = Size::from_str(input); | ||
let expected = Size::from_bytes(12.34 * crate::consts::KB as f64); | ||
assert_eq!(parsed, Ok(expected)); | ||
} | ||
|
||
#[test] | ||
fn parse() { | ||
let size = "12.34 kIloByte".parse(); | ||
assert_eq!(size, Ok(Size::from_bytes(12 * KB + 340))); | ||
} | ||
|
||
impl FromStr for Size { | ||
type Err = ParseSizeError; | ||
|
||
fn from_str(s: &str) -> Result<Size, Self::Err> { | ||
let s = s.trim(); | ||
if s.is_empty() { | ||
return Err(ParseSizeError); | ||
} | ||
|
||
let (number_str, unit) = match s.split_once(' ') { | ||
None => { | ||
// Possibly in the format 2mib (no separating space) | ||
// Try to split at the first non-numeric value. | ||
match s.find(|c: char| !c.is_ascii_digit() && c != '.') { | ||
None => (s, ""), // just a number, no unit | ||
Some(idx) => s.split_at(dbg!(idx)), | ||
} | ||
} | ||
Some((num, unit)) => (num, unit), | ||
}; | ||
let number: f64 = number_str.parse().map_err(|_| ParseSizeError)?; | ||
|
||
let unit = unit.trim().to_lowercase(); | ||
let multiplier = match unit.as_str().trim_end_matches('s') { | ||
"" | "b" | "byte" => B, | ||
"kb" | "kilobyte" => KB, | ||
"mb" | "megabyte" => MB, | ||
"gb" | "gigabyte" => GB, | ||
"tb" | "terabyte" => TB, | ||
"pb" | "petabyte" => PB, | ||
"eb" | "exabyte" => EB, | ||
|
||
"kib" | "kibibyte" => KiB, | ||
"mib" | "mebibyte" => MiB, | ||
"gib" | "gibibyte" => GiB, | ||
"tib" | "tebibyte" => TiB, | ||
"pib" | "pebibyte" => PiB, | ||
"eib" | "exbibyte" => EiB, | ||
|
||
_ => return Err(ParseSizeError), | ||
}; | ||
|
||
Ok(Size::from_bytes(number * multiplier as f64)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn parse_bare_bytes() { | ||
assert_eq!(Size::from_str("1234"), Ok(Size { bytes: 1234 })); | ||
assert_eq!(Size::from_str(" 1234 "), Ok(Size { bytes: 1234 })); // Leading and trailing whitespace | ||
} | ||
|
||
#[test] | ||
fn parse_abbr_unit() { | ||
let tests = vec![ | ||
("1234B", 1234), | ||
("1234 KB", 1234 * KB), | ||
("1234KiB", 1234 * KiB), | ||
("12.34 MB", (12.34 * MB as f64) as i64), | ||
("12.34MiB", (12.34 * MiB as f64) as i64), | ||
(" 1234 GB ", 1234 * GB), | ||
]; | ||
|
||
for (input, expected) in tests { | ||
assert_eq!(Size::from_str(input), Ok(Size { bytes: expected })); | ||
} | ||
} | ||
|
||
#[test] | ||
fn parse_full_unit() { | ||
let tests = vec![ | ||
("1234 bytes", 1234), | ||
("1234 kilobytes", 1234 * KB), | ||
("1234 kibibytes", 1234 * KiB), | ||
("12.34 gigabytes", (12.34 * GB as f64) as i64), | ||
("12.34 gibibytes", (12.34 * GiB as f64) as i64), | ||
]; | ||
|
||
for (input, expected) in tests { | ||
assert_eq!(Size::from_str(input), Ok(Size { bytes: expected })); | ||
} | ||
} | ||
|
||
#[test] | ||
fn parse_invalid_inputs() { | ||
let tests = vec![ | ||
"Not a number", | ||
"1234 XB", // Unknown suffix | ||
"12..34 MB", // Invalid number format | ||
]; | ||
|
||
for input in tests { | ||
assert_eq!(dbg!(Size::from_str(input)), Err(ParseSizeError)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters