diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-23 13:00:14 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-23 13:00:14 +0200 |
commit | d21a523ab459f7a6893e85822d0ef5d4ec795949 (patch) | |
tree | ccd11c284b7f01576b586d5f18798b7c20905255 /crates | |
parent | refactor(libmpv2): Move to the `crates` directory (diff) | |
download | yt-d21a523ab459f7a6893e85822d0ef5d4ec795949.tar.gz yt-d21a523ab459f7a6893e85822d0ef5d4ec795949.zip |
feat(crates/bytes): Init
Diffstat (limited to 'crates')
-rw-r--r-- | crates/bytes/.gitignore | 11 | ||||
-rw-r--r-- | crates/bytes/Cargo.lock | 7 | ||||
-rw-r--r-- | crates/bytes/Cargo.lock.license | 9 | ||||
-rw-r--r-- | crates/bytes/Cargo.toml | 16 | ||||
-rw-r--r-- | crates/bytes/src/error.rs | 38 | ||||
-rw-r--r-- | crates/bytes/src/lib.rs | 215 |
6 files changed, 296 insertions, 0 deletions
diff --git a/crates/bytes/.gitignore b/crates/bytes/.gitignore new file mode 100644 index 0000000..8876ea6 --- /dev/null +++ b/crates/bytes/.gitignore @@ -0,0 +1,11 @@ +# yt - A fully featured command line YouTube client +# +# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of Yt. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +/target diff --git a/crates/bytes/Cargo.lock b/crates/bytes/Cargo.lock new file mode 100644 index 0000000..b819c60 --- /dev/null +++ b/crates/bytes/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bytes" +version = "1.0.0" diff --git a/crates/bytes/Cargo.lock.license b/crates/bytes/Cargo.lock.license new file mode 100644 index 0000000..d4d410f --- /dev/null +++ b/crates/bytes/Cargo.lock.license @@ -0,0 +1,9 @@ +yt - A fully featured command line YouTube client + +Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: GPL-3.0-or-later + +This file is part of Yt. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. diff --git a/crates/bytes/Cargo.toml b/crates/bytes/Cargo.toml new file mode 100644 index 0000000..98691ee --- /dev/null +++ b/crates/bytes/Cargo.toml @@ -0,0 +1,16 @@ +# yt - A fully featured command line YouTube client +# +# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of Yt. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +[package] +name = "bytes" +version = "1.0.0" +edition = "2021" +license = "GPL-3.0-or-later" +description = "Simple byte formatting utilities" diff --git a/crates/bytes/src/error.rs b/crates/bytes/src/error.rs new file mode 100644 index 0000000..7643109 --- /dev/null +++ b/crates/bytes/src/error.rs @@ -0,0 +1,38 @@ +// yt - A fully featured command line YouTube client +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Yt. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +use std::{fmt::Display, num::ParseIntError}; + +#[derive(Debug)] +pub enum BytesError { + BytesParseIntError(ParseIntError), + NotYetSupported(String), +} + +impl Display for BytesError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BytesError::BytesParseIntError(e) => { + f.write_fmt(format_args!("Failed to parse a number as integer: '{}'", e)) + }, + BytesError::NotYetSupported(other) => { + f.write_fmt(format_args!("Your extension '{}' is not yet supported. Only KB,MB,GB or KiB,MiB,GiB are supported", other)) + } + } + } +} + +impl From<ParseIntError> for BytesError { + fn from(value: ParseIntError) -> Self { + Self::BytesParseIntError(value) + } +} + +impl std::error::Error for BytesError {} diff --git a/crates/bytes/src/lib.rs b/crates/bytes/src/lib.rs new file mode 100644 index 0000000..f80b864 --- /dev/null +++ b/crates/bytes/src/lib.rs @@ -0,0 +1,215 @@ +// yt - A fully featured command line YouTube client +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Yt. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +use std::{fmt::Display, str::FromStr}; + +use error::BytesError; + +const B: u64 = 1; + +const KIB: u64 = 1024 * B; +const MIB: u64 = 1024 * KIB; +const GIB: u64 = 1024 * MIB; +const TIB: u64 = 1024 * GIB; +const PIB: u64 = 1024 * TIB; + +const KB: u64 = 1000 * B; +const MB: u64 = 1000 * KB; +const GB: u64 = 1000 * MB; +const TB: u64 = 1000 * GB; + +pub mod error; + +#[derive(Clone, Copy)] +pub struct Bytes(u64); + +impl Bytes { + pub fn as_u64(self) -> u64 { + self.0 + } + pub fn new(v: u64) -> Self { + Self(v) + } +} + +impl FromStr for Bytes { + type Err = BytesError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let s = s + .chars() + .filter(|elem| !elem.is_whitespace()) + .collect::<String>(); + + let number: u64 = s + .chars() + .take_while(|x| x.is_numeric()) + .collect::<String>() + .parse()?; + let extension = s.chars().skip_while(|x| x.is_numeric()).collect::<String>(); + + let output = match extension.to_lowercase().as_str() { + "" => number, + "b" => number * B, + "kib" => number * KIB, + "mib" => number * MIB, + "gib" => number * GIB, + "tib" => number * TIB, + "kb" => number * KB, + "mb" => number * MB, + "gb" => number * GB, + "tb" => number * TB, + other => return Err(BytesError::NotYetSupported(other.to_owned())), + }; + + Ok(Self(output)) + } +} + +impl Display for Bytes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let num = self.0; + + match num { + 0..KIB => f.write_fmt(format_args!("{} {}", num, "B"))?, + KIB..MIB => f.write_fmt(format_args!( + "{} {}", + precision_f64((num as f64) / (KIB as f64), 3), + "KiB" + ))?, + MIB..GIB => f.write_fmt(format_args!( + "{} {}", + precision_f64((num as f64) / (MIB as f64), 3), + "MiB" + ))?, + GIB..TIB => f.write_fmt(format_args!( + "{} {}", + precision_f64((num as f64) / (GIB as f64), 3), + "GiB" + ))?, + TIB..PIB => f.write_fmt(format_args!( + "{} {}", + precision_f64((num as f64) / (TIB as f64), 3), + "TiB" + ))?, + PIB.. => todo!(), + } + + Ok(()) + } +} + +// taken from this stack overflow question: https://stackoverflow.com/a/76572321 +/// Round to significant digits (rather than digits after the decimal). +/// +/// Not implemented for `f32`, because such an implementation showed precision +/// glitches (e.g. `precision_f32(12300.0, 2) == 11999.999`), so for `f32` +/// floats, convert to `f64` for this function and back as needed. +/// +/// Examples: +/// ``` +///# fn main() { +///# use bytes::precision_f64; +/// assert_eq!(precision_f64(1.2300, 2), 1.2f64); +/// assert_eq!(precision_f64(1.2300_f64, 2), 1.2f64); +/// assert_eq!(precision_f64(1.2300_f32 as f64, 2), 1.2f64); +/// assert_eq!(precision_f64(1.2300_f32 as f64, 2) as f32, 1.2f32); +///# } +/// ``` +pub fn precision_f64(x: f64, decimals: u32) -> f64 { + if x == 0. || decimals == 0 { + 0. + } else { + let shift = decimals as i32 - x.abs().log10().ceil() as i32; + let shift_factor = 10_f64.powi(shift); + + (x * shift_factor).round() / shift_factor + } +} + +#[cfg(test)] +mod tests { + use super::{Bytes, GIB}; + + #[test] + fn parsing() { + let input: Bytes = "20 GiB".parse().unwrap(); + let expected = 20 * GIB; + + assert_eq!(expected, input.0); + } + #[test] + fn round_trip_1kib() { + let input = "1 KiB"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!(input.to_owned(), parsed.to_string()); + } + #[test] + fn round_trip_2kib() { + let input = "2 KiB"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!(input.to_owned(), parsed.to_string()); + } + + #[test] + fn round_trip_1mib() { + let input = "1 MiB"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!(input.to_owned(), parsed.to_string()); + } + #[test] + fn round_trip_2mib() { + let input = "2 MiB"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!(input.to_owned(), parsed.to_string()); + } + + #[test] + fn round_trip_1gib() { + let input = "1 GiB"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!(input.to_owned(), parsed.to_string()); + } + #[test] + fn round_trip_2gib() { + let input = "2 GiB"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!(input.to_owned(), parsed.to_string()); + } + + #[test] + fn round_trip() { + let input = "20 TiB"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!(input.to_owned(), parsed.to_string()); + } + + #[test] + fn round_trip_decmimal() { + let input = "20 TB"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!("18.2 TiB", parsed.to_string()); + } + #[test] + fn round_trip_1b() { + let input = "1"; + let parsed: Bytes = input.parse().unwrap(); + + assert_eq!("1 B", parsed.to_string()); + } +} |