Skip to content

Commit 3ccde80

Browse files
committed
Implement no_std support with simplified to_string
1 parent 5235bf4 commit 3ccde80

File tree

3 files changed

+102
-21
lines changed

3 files changed

+102
-21
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ serde_json = "1.0"
2020
toml = "0.5"
2121

2222
[features]
23-
default = []
23+
default = ["std"]
24+
std = []
2425
serde = ["dep:serde"]

src/lib.rs

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,24 @@
2626
//! assert_eq!("482 GiB".to_string(), ByteSize::gb(518).to_string(true));
2727
//! assert_eq!("518 GB".to_string(), ByteSize::gb(518).to_string(false));
2828
//! ```
29+
#![cfg_attr(not(feature = "std"), no_std)]
2930

3031
mod parse;
3132

33+
#[cfg(any(feature = "std", not(all(not(feature = "std"), test))))]
34+
extern crate core;
35+
#[cfg(all(not(feature = "std"), test))]
36+
extern crate std;
37+
3238
#[cfg(feature = "serde")]
3339
extern crate serde;
3440
#[cfg(feature = "serde")]
3541
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
3642
#[cfg(feature = "serde")]
3743
use std::convert::TryFrom;
3844

39-
use std::fmt::{self, Debug, Display, Formatter};
40-
use std::ops::{Add, AddAssign, Mul, MulAssign};
45+
use core::fmt::{self, Debug, Display, Formatter};
46+
use core::ops::{Add, AddAssign, Mul, MulAssign};
4147

4248
/// byte size for 1 byte
4349
pub const B: u64 = 1;
@@ -173,13 +179,33 @@ impl ByteSize {
173179
self.0
174180
}
175181

182+
#[cfg(feature = "std")]
176183
#[inline(always)]
177184
pub fn to_string_as(&self, si_unit: bool) -> String {
178185
to_string(self.0, si_unit)
179186
}
180187
}
181188

182-
pub fn to_string(bytes: u64, si_prefix: bool) -> String {
189+
// Used to implement `Display` in `no_std` environment
190+
struct BytePrinter(u64, bool);
191+
impl Display for BytePrinter {
192+
fn fmt(&self, f: &mut Formatter) ->fmt::Result {
193+
to_string_fmt(self.0, self.1, f)
194+
}
195+
}
196+
197+
fn to_string_fmt(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt::Result {
198+
let unit = if si_prefix { KIB } else { KB };
199+
200+
if bytes < unit {
201+
write!(f, "{} B", bytes)
202+
} else {
203+
to_string_decimal(bytes, si_prefix, f)
204+
}
205+
}
206+
207+
#[cfg(feature = "std")]
208+
fn to_string_decimal(bytes: u64, si_prefix: bool, f: &mut fmt::Formatter) -> fmt::Result {
183209
let unit = if si_prefix { KIB } else { KB };
184210
let unit_base = if si_prefix { LN_KIB } else { LN_KB };
185211
let unit_prefix = if si_prefix {
@@ -189,27 +215,73 @@ pub fn to_string(bytes: u64, si_prefix: bool) -> String {
189215
};
190216
let unit_suffix = if si_prefix { "iB" } else { "B" };
191217

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

267+
#[cfg(feature = "std")]
268+
pub fn to_string(bytes: u64, si_prefix: bool) -> String {
269+
BytePrinter(bytes, si_prefix).to_string()
270+
}
271+
272+
// `no_std` padding support would require writing to an intermediary buffer
273+
// as well as implementing said buffer.
274+
// So we just drop padding support in `no_std` environments
210275
impl Display for ByteSize {
276+
#[cfg(feature = "std")]
211277
fn fmt(&self, f: &mut Formatter) ->fmt::Result {
212-
f.pad(&to_string(self.0, false))
278+
f.pad(&BytePrinter(self.0, false).to_string())
279+
}
280+
281+
#[cfg(not(feature = "std"))]
282+
fn fmt(&self, f: &mut Formatter) ->fmt::Result {
283+
284+
to_string_fmt(self.0, false, f)
213285
}
214286
}
215287

@@ -362,6 +434,7 @@ impl Serialize for ByteSize {
362434
mod tests {
363435
use super::*;
364436

437+
use std::format;
365438
#[test]
366439
fn test_arithmetic_op() {
367440
let mut x = ByteSize::mb(1);
@@ -408,6 +481,7 @@ mod tests {
408481
}
409482

410483
fn assert_display(expected: &str, b: ByteSize) {
484+
411485
assert_eq!(expected, format!("{}", b));
412486
}
413487

@@ -422,6 +496,7 @@ mod tests {
422496
assert_display("609.0 PB", ByteSize::pb(609));
423497
}
424498

499+
#[cfg(feature = "std")]
425500
#[test]
426501
fn test_display_alignment() {
427502
assert_eq!("|357 B |", format!("|{:10}|", ByteSize(357)));
@@ -434,10 +509,12 @@ mod tests {
434509
assert_eq!("|--357 B---|", format!("|{:-^10}|", ByteSize(357)));
435510
}
436511

512+
#[cfg(feature = "std")]
437513
fn assert_to_string(expected: &str, b: ByteSize, si: bool) {
438514
assert_eq!(expected.to_string(), b.to_string_as(si));
439515
}
440516

517+
#[cfg(feature = "std")]
441518
#[test]
442519
fn test_to_string_as() {
443520
assert_to_string("215 B", ByteSize::b(215), true);
@@ -474,6 +551,7 @@ mod tests {
474551
assert_eq!(ByteSize::b(0), ByteSize::default());
475552
}
476553

554+
#[cfg(feature = "std")]
477555
#[test]
478556
fn test_to_string() {
479557
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

@@ -71,7 +72,7 @@ impl Unit {
7172

7273
mod impl_ops {
7374
use super::Unit;
74-
use std::ops;
75+
use core::ops;
7576

7677
impl ops::Add<u64> for Unit {
7778
type Output = u64;
@@ -138,6 +139,7 @@ mod impl_ops {
138139
}
139140
}
140141

142+
#[cfg(feature = "std")]
141143
impl std::str::FromStr for Unit {
142144
type Err = String;
143145

@@ -161,7 +163,7 @@ impl std::str::FromStr for Unit {
161163
}
162164
}
163165

164-
#[cfg(test)]
166+
#[cfg(all(test, feature = "std"))]
165167
mod tests {
166168
use super::*;
167169

0 commit comments

Comments
 (0)