From 55df4a81aded15b121074c3d1f7cc339b24ff9b5 Mon Sep 17 00:00:00 2001 From: AdnoC Date: Tue, 22 Aug 2023 21:59:41 -0400 Subject: [PATCH 1/5] Implement no_std support with simplified to_string --- Cargo.toml | 3 +- src/lib.rs | 117 ++++++++++++++++++++++++++++++++++++++++++--------- src/parse.rs | 6 ++- 3 files changed, 104 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8a6beb8..18b81a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ serde_json = "1.0.105" toml = "0.7.6" [features] +default = ["std"] arbitrary = ["dep:arbitrary"] -default = [] +std = [] serde = ["dep:serde"] diff --git a/src/lib.rs b/src/lib.rs index edf5f97..f283280 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,11 +28,18 @@ //! assert_eq!("482.4 GiB", ByteSize::gb(518).to_string_as(true)); //! assert_eq!("518.0 GB", ByteSize::gb(518).to_string_as(false)); //! ``` +#![cfg_attr(not(feature = "std"), no_std)] mod parse; #[cfg(feature = "arbitrary")] extern crate arbitrary; + +#[cfg(any(feature = "std", not(all(not(feature = "std"), test))))] +extern crate core; +#[cfg(all(not(feature = "std"), test))] +extern crate std; + #[cfg(feature = "serde")] extern crate serde; #[cfg(feature = "serde")] @@ -40,8 +47,8 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde")] use std::convert::TryFrom; -use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::{Add, AddAssign, Mul, MulAssign}; +use core::fmt::{self, Debug, Display, Formatter}; +use core::ops::{Add, AddAssign, Mul, MulAssign}; /// byte size for 1 byte pub const B: u64 = 1; @@ -178,13 +185,33 @@ impl ByteSize { self.0 } + #[cfg(feature = "std")] #[inline(always)] pub fn to_string_as(&self, si_unit: bool) -> String { to_string(self.0, si_unit) } } -pub fn to_string(bytes: u64, si_prefix: bool) -> String { +// Used to implement `Display` in `no_std` environment +struct BytePrinter(u64, bool); +impl Display for BytePrinter { + fn fmt(&self, f: &mut Formatter) ->fmt::Result { + to_string_fmt(self.0, self.1, f) + } +} + +fn to_string_fmt(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt::Result { + let unit = if si_prefix { KIB } else { KB }; + + if bytes < unit { + write!(f, "{} B", bytes) + } else { + to_string_decimal(bytes, si_prefix, f) + } +} + +#[cfg(feature = "std")] +fn to_string_decimal(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt::Result { let unit = if si_prefix { KIB } else { KB }; let unit_base = if si_prefix { LN_KIB } else { LN_KB }; let unit_prefix = if si_prefix { @@ -194,27 +221,73 @@ pub fn to_string(bytes: u64, si_prefix: bool) -> String { }; let unit_suffix = if si_prefix { "iB" } else { "B" }; - if bytes < unit { - format!("{} B", bytes) + let size = bytes as f64; + let exp = match (size.ln() / unit_base) as usize { + e if e == 0 => 1, + e => e, + }; + + write!( + f, + "{:.1} {}{}", + (size / unit.pow(exp as u32) as f64), + unit_prefix[exp - 1] as char, + unit_suffix + ) +} + +// Simplified algorithm because `no_std` does not have access to `f32::ln()` +#[cfg(not(feature = "std"))] +fn to_string_decimal(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt::Result { + let unit_sizes = if si_prefix { + [KIB, MIB, GIB, TIB, PIB] } else { - let size = bytes as f64; - let exp = match (size.ln() / unit_base) as usize { - e if e == 0 => 1, - e => e, - }; - - format!( - "{:.1} {}{}", - (size / unit.pow(exp as u32) as f64), - unit_prefix[exp - 1] as char, - unit_suffix - ) + [KB, MB, GB, TB, PB] + }; + let unit_prefix = if si_prefix { + UNITS_SI.as_bytes() + } else { + UNITS.as_bytes() + }; + let mut ideal_size = unit_sizes[0]; + let mut ideal_prefix = unit_prefix[0]; + for (&size, &prefix) in unit_sizes.iter().zip(unit_prefix.iter()) { + ideal_size = size; + ideal_prefix = prefix; + if size <= bytes && bytes / 1_000 < size { + break; + } } + + let unit_suffix = if si_prefix { "iB" } else { "B" }; + + write!( + f, + "{:.1} {}{}", + bytes as f64 / ideal_size as f64, + ideal_prefix as char, + unit_suffix + ) } +#[cfg(feature = "std")] +pub fn to_string(bytes: u64, si_prefix: bool) -> String { + BytePrinter(bytes, si_prefix).to_string() +} + +// `no_std` padding support would require writing to an intermediary buffer +// as well as implementing said buffer. +// So we just drop padding support in `no_std` environments impl Display for ByteSize { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(&to_string(self.0, false)) + #[cfg(feature = "std")] + fn fmt(&self, f: &mut Formatter) ->fmt::Result { + f.pad(&BytePrinter(self.0, false).to_string()) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, f: &mut Formatter) ->fmt::Result { + + to_string_fmt(self.0, false, f) } } @@ -375,6 +448,7 @@ impl Serialize for ByteSize { mod tests { use super::*; + use std::format; #[test] fn test_arithmetic_op() { let mut x = ByteSize::mb(1); @@ -421,6 +495,7 @@ mod tests { } fn assert_display(expected: &str, b: ByteSize) { + assert_eq!(expected, format!("{}", b)); } @@ -435,6 +510,7 @@ mod tests { assert_display("609.0 PB", ByteSize::pb(609)); } + #[cfg(feature = "std")] #[test] fn test_display_alignment() { assert_eq!("|357 B |", format!("|{:10}|", ByteSize(357))); @@ -447,10 +523,12 @@ mod tests { assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357))); } + #[cfg(feature = "std")] fn assert_to_string(expected: &str, b: ByteSize, si: bool) { assert_eq!(expected.to_string(), b.to_string_as(si)); } + #[cfg(feature = "std")] #[test] fn test_to_string_as() { assert_to_string("215 B", ByteSize::b(215), true); @@ -487,6 +565,7 @@ mod tests { assert_eq!(ByteSize::b(0), ByteSize::default()); } + #[cfg(feature = "std")] #[test] fn test_to_string() { assert_to_string("609.0 PB", ByteSize::pb(609), false); diff --git a/src/parse.rs b/src/parse.rs index a5cca5b..94d8dba 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,5 +1,6 @@ use super::ByteSize; +#[cfg(feature = "std")] impl std::str::FromStr for ByteSize { type Err = String; @@ -91,7 +92,7 @@ impl Unit { mod impl_ops { use super::Unit; - use std::ops; + use core::ops; impl ops::Add for Unit { type Output = u64; @@ -158,6 +159,7 @@ mod impl_ops { } } +#[cfg(feature = "std")] impl std::str::FromStr for Unit { type Err = String; @@ -181,7 +183,7 @@ impl std::str::FromStr for Unit { } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use super::*; From df87212f0c170484b86c76517fc4ee51e78162e7 Mon Sep 17 00:00:00 2001 From: AdnoC Date: Tue, 22 Aug 2023 22:13:27 -0400 Subject: [PATCH 2/5] update ci script --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ddd1586..77a44b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,4 @@ script: - cargo fmt --all -- --check - cargo build --verbose --all - cargo test --verbose --all + - cargo test --no-default-features --verbose --all From f4f36ecb0620cc291c6e1528f61806a95f057dea Mon Sep 17 00:00:00 2001 From: AdnoC Date: Wed, 23 Aug 2023 14:11:16 -0400 Subject: [PATCH 3/5] use correct divisor --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f283280..fb2a9a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -254,7 +254,7 @@ fn to_string_decimal(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt for (&size, &prefix) in unit_sizes.iter().zip(unit_prefix.iter()) { ideal_size = size; ideal_prefix = prefix; - if size <= bytes && bytes / 1_000 < size { + if size <= bytes && bytes / unit_sizes[0] < size { break; } } From c1216c5f969df95e818575204ee5fbe95b228fd8 Mon Sep 17 00:00:00 2001 From: AdnoC Date: Wed, 23 Aug 2023 14:16:44 -0400 Subject: [PATCH 4/5] fix core import error --- src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fb2a9a1..f9e40ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,8 +35,10 @@ mod parse; #[cfg(feature = "arbitrary")] extern crate arbitrary; -#[cfg(any(feature = "std", not(all(not(feature = "std"), test))))] -extern crate core; +// Alias `std` as core when `std` is enabled +#[cfg(feature = "std")] +use std as core; +// In tests bring in `std` even when `no_std` #[cfg(all(not(feature = "std"), test))] extern crate std; @@ -44,7 +46,7 @@ extern crate std; extern crate serde; #[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -#[cfg(feature = "serde")] +#[cfg(all(feature = "std", feature = "serde"))] use std::convert::TryFrom; use core::fmt::{self, Debug, Display, Formatter}; From 51bd017bc58def657c71a3ecbb9c6e6d8bdfedfc Mon Sep 17 00:00:00 2001 From: AdnoC Date: Thu, 7 Sep 2023 11:43:09 -0400 Subject: [PATCH 5/5] doctest requires std --- src/lib.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f9e40ef..f0fab3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,14 +20,18 @@ //! } //! ``` //! -//! It also provides its human readable string as follows: -//! -//! ``` -//! use bytesize::ByteSize; -//! -//! assert_eq!("482.4 GiB", ByteSize::gb(518).to_string_as(true)); -//! assert_eq!("518.0 GB", ByteSize::gb(518).to_string_as(false)); -//! ``` +#![cfg_attr(feature = "std", doc = r#" +It also provides its human readable string as follows: + +``` +use bytesize::ByteSize; + +assert_eq!("482.4 GiB", ByteSize::gb(518).to_string_as(true)); +assert_eq!("518.0 GB", ByteSize::gb(518).to_string_as(false)); +``` +"#)] + + #![cfg_attr(not(feature = "std"), no_std)] mod parse;