Skip to content

Commit 55df4a8

Browse files
committed
Implement no_std support with simplified to_string
1 parent 6630ad4 commit 55df4a8

File tree

3 files changed

+104
-22
lines changed

3 files changed

+104
-22
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ serde_json = "1.0.105"
2121
toml = "0.7.6"
2222

2323
[features]
24+
default = ["std"]
2425
arbitrary = ["dep:arbitrary"]
25-
default = []
26+
std = []
2627
serde = ["dep:serde"]

src/lib.rs

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,27 @@
2828
//! assert_eq!("482.4 GiB", ByteSize::gb(518).to_string_as(true));
2929
//! assert_eq!("518.0 GB", ByteSize::gb(518).to_string_as(false));
3030
//! ```
31+
#![cfg_attr(not(feature = "std"), no_std)]
3132

3233
mod parse;
3334

3435
#[cfg(feature = "arbitrary")]
3536
extern crate arbitrary;
37+
38+
#[cfg(any(feature = "std", not(all(not(feature = "std"), test))))]
39+
extern crate core;
40+
#[cfg(all(not(feature = "std"), test))]
41+
extern crate std;
42+
3643
#[cfg(feature = "serde")]
3744
extern crate serde;
3845
#[cfg(feature = "serde")]
3946
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
4047
#[cfg(feature = "serde")]
4148
use std::convert::TryFrom;
4249

43-
use std::fmt::{self, Debug, Display, Formatter};
44-
use std::ops::{Add, AddAssign, Mul, MulAssign};
50+
use core::fmt::{self, Debug, Display, Formatter};
51+
use core::ops::{Add, AddAssign, Mul, MulAssign};
4552

4653
/// byte size for 1 byte
4754
pub const B: u64 = 1;
@@ -178,13 +185,33 @@ impl ByteSize {
178185
self.0
179186
}
180187

188+
#[cfg(feature = "std")]
181189
#[inline(always)]
182190
pub fn to_string_as(&self, si_unit: bool) -> String {
183191
to_string(self.0, si_unit)
184192
}
185193
}
186194

187-
pub fn to_string(bytes: u64, si_prefix: bool) -> String {
195+
// Used to implement `Display` in `no_std` environment
196+
struct BytePrinter(u64, bool);
197+
impl Display for BytePrinter {
198+
fn fmt(&self, f: &mut Formatter) ->fmt::Result {
199+
to_string_fmt(self.0, self.1, f)
200+
}
201+
}
202+
203+
fn to_string_fmt(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt::Result {
204+
let unit = if si_prefix { KIB } else { KB };
205+
206+
if bytes < unit {
207+
write!(f, "{} B", bytes)
208+
} else {
209+
to_string_decimal(bytes, si_prefix, f)
210+
}
211+
}
212+
213+
#[cfg(feature = "std")]
214+
fn to_string_decimal(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt::Result {
188215
let unit = if si_prefix { KIB } else { KB };
189216
let unit_base = if si_prefix { LN_KIB } else { LN_KB };
190217
let unit_prefix = if si_prefix {
@@ -194,27 +221,73 @@ pub fn to_string(bytes: u64, si_prefix: bool) -> String {
194221
};
195222
let unit_suffix = if si_prefix { "iB" } else { "B" };
196223

197-
if bytes < unit {
198-
format!("{} B", bytes)
224+
let size = bytes as f64;
225+
let exp = match (size.ln() / unit_base) as usize {
226+
e if e == 0 => 1,
227+
e => e,
228+
};
229+
230+
write!(
231+
f,
232+
"{:.1} {}{}",
233+
(size / unit.pow(exp as u32) as f64),
234+
unit_prefix[exp - 1] as char,
235+
unit_suffix
236+
)
237+
}
238+
239+
// Simplified algorithm because `no_std` does not have access to `f32::ln()`
240+
#[cfg(not(feature = "std"))]
241+
fn to_string_decimal(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt::Result {
242+
let unit_sizes = if si_prefix {
243+
[KIB, MIB, GIB, TIB, PIB]
199244
} else {
200-
let size = bytes as f64;
201-
let exp = match (size.ln() / unit_base) as usize {
202-
e if e == 0 => 1,
203-
e => e,
204-
};
205-
206-
format!(
207-
"{:.1} {}{}",
208-
(size / unit.pow(exp as u32) as f64),
209-
unit_prefix[exp - 1] as char,
210-
unit_suffix
211-
)
245+
[KB, MB, GB, TB, PB]
246+
};
247+
let unit_prefix = if si_prefix {
248+
UNITS_SI.as_bytes()
249+
} else {
250+
UNITS.as_bytes()
251+
};
252+
let mut ideal_size = unit_sizes[0];
253+
let mut ideal_prefix = unit_prefix[0];
254+
for (&size, &prefix) in unit_sizes.iter().zip(unit_prefix.iter()) {
255+
ideal_size = size;
256+
ideal_prefix = prefix;
257+
if size <= bytes && bytes / 1_000 < size {
258+
break;
259+
}
212260
}
261+
262+
let unit_suffix = if si_prefix { "iB" } else { "B" };
263+
264+
write!(
265+
f,
266+
"{:.1} {}{}",
267+
bytes as f64 / ideal_size as f64,
268+
ideal_prefix as char,
269+
unit_suffix
270+
)
213271
}
214272

273+
#[cfg(feature = "std")]
274+
pub fn to_string(bytes: u64, si_prefix: bool) -> String {
275+
BytePrinter(bytes, si_prefix).to_string()
276+
}
277+
278+
// `no_std` padding support would require writing to an intermediary buffer
279+
// as well as implementing said buffer.
280+
// So we just drop padding support in `no_std` environments
215281
impl Display for ByteSize {
216-
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
217-
f.pad(&to_string(self.0, false))
282+
#[cfg(feature = "std")]
283+
fn fmt(&self, f: &mut Formatter) ->fmt::Result {
284+
f.pad(&BytePrinter(self.0, false).to_string())
285+
}
286+
287+
#[cfg(not(feature = "std"))]
288+
fn fmt(&self, f: &mut Formatter) ->fmt::Result {
289+
290+
to_string_fmt(self.0, false, f)
218291
}
219292
}
220293

@@ -375,6 +448,7 @@ impl Serialize for ByteSize {
375448
mod tests {
376449
use super::*;
377450

451+
use std::format;
378452
#[test]
379453
fn test_arithmetic_op() {
380454
let mut x = ByteSize::mb(1);
@@ -421,6 +495,7 @@ mod tests {
421495
}
422496

423497
fn assert_display(expected: &str, b: ByteSize) {
498+
424499
assert_eq!(expected, format!("{}", b));
425500
}
426501

@@ -435,6 +510,7 @@ mod tests {
435510
assert_display("609.0 PB", ByteSize::pb(609));
436511
}
437512

513+
#[cfg(feature = "std")]
438514
#[test]
439515
fn test_display_alignment() {
440516
assert_eq!("|357 B |", format!("|{:10}|", ByteSize(357)));
@@ -447,10 +523,12 @@ mod tests {
447523
assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
448524
}
449525

526+
#[cfg(feature = "std")]
450527
fn assert_to_string(expected: &str, b: ByteSize, si: bool) {
451528
assert_eq!(expected.to_string(), b.to_string_as(si));
452529
}
453530

531+
#[cfg(feature = "std")]
454532
#[test]
455533
fn test_to_string_as() {
456534
assert_to_string("215 B", ByteSize::b(215), true);
@@ -487,6 +565,7 @@ mod tests {
487565
assert_eq!(ByteSize::b(0), ByteSize::default());
488566
}
489567

568+
#[cfg(feature = "std")]
490569
#[test]
491570
fn test_to_string() {
492571
assert_to_string("609.0 PB", ByteSize::pb(609), false);

src/parse.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::ByteSize;
22

3+
#[cfg(feature = "std")]
34
impl std::str::FromStr for ByteSize {
45
type Err = String;
56

@@ -91,7 +92,7 @@ impl Unit {
9192

9293
mod impl_ops {
9394
use super::Unit;
94-
use std::ops;
95+
use core::ops;
9596

9697
impl ops::Add<u64> for Unit {
9798
type Output = u64;
@@ -158,6 +159,7 @@ mod impl_ops {
158159
}
159160
}
160161

162+
#[cfg(feature = "std")]
161163
impl std::str::FromStr for Unit {
162164
type Err = String;
163165

@@ -181,7 +183,7 @@ impl std::str::FromStr for Unit {
181183
}
182184
}
183185

184-
#[cfg(test)]
186+
#[cfg(all(test, feature = "std"))]
185187
mod tests {
186188
use super::*;
187189

0 commit comments

Comments
 (0)