Skip to content

Commit 6e422b7

Browse files
authored
df: add tracing zero and rounding (uutils#8685)
1 parent 517b5fb commit 6e422b7

File tree

3 files changed

+214
-50
lines changed

3 files changed

+214
-50
lines changed

src/uu/df/src/blocks.rs

Lines changed: 162 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,13 @@ impl SuffixType {
7777
/// Convert a number into a magnitude and a multi-byte unit suffix.
7878
///
7979
/// The returned string has a maximum length of 5 chars, for example: "1.1kB", "999kB", "1MB".
80-
pub(crate) fn to_magnitude_and_suffix(n: u128, suffix_type: SuffixType) -> String {
80+
/// `add_tracing_zero` allows to add tracing zero for values in 0 < x <= 9
81+
///
82+
pub(crate) fn to_magnitude_and_suffix(
83+
n: u128,
84+
suffix_type: SuffixType,
85+
add_tracing_zero: bool,
86+
) -> String {
8187
let bases = suffix_type.bases();
8288
let suffixes = suffix_type.suffixes();
8389
let mut i = 0;
@@ -91,14 +97,25 @@ pub(crate) fn to_magnitude_and_suffix(n: u128, suffix_type: SuffixType) -> Strin
9197
let suffix = suffixes[i];
9298

9399
if rem == 0 {
94-
format!("{quot}{suffix}")
100+
if add_tracing_zero && !suffix.is_empty() && quot != 0 && quot <= 9 {
101+
format!("{quot}.0{suffix}")
102+
} else {
103+
format!("{quot}{suffix}")
104+
}
95105
} else {
96106
let tenths_place = rem / (bases[i] / 10);
97107

98-
if rem % (bases[i] / 10) == 0 {
108+
if quot >= 100 && rem > 0 {
109+
format!("{}{suffix}", quot + 1)
110+
} else if rem % (bases[i] / 10) == 0 {
99111
format!("{quot}.{tenths_place}{suffix}")
100112
} else if tenths_place + 1 == 10 || quot >= 10 {
101-
format!("{}{suffix}", quot + 1)
113+
let quot = quot + 1;
114+
if add_tracing_zero && !suffix.is_empty() && quot <= 9 {
115+
format!("{quot}.0{suffix}")
116+
} else {
117+
format!("{quot}{suffix}")
118+
}
102119
} else {
103120
format!("{quot}.{}{suffix}", tenths_place + 1)
104121
}
@@ -150,6 +167,18 @@ impl BlockSize {
150167
Self::Bytes(n) => n,
151168
}
152169
}
170+
171+
pub(crate) fn to_header(&self) -> String {
172+
match self {
173+
Self::Bytes(n) => {
174+
if n % 1024 == 0 && n % 1000 != 0 {
175+
to_magnitude_and_suffix(*n as u128, SuffixType::Iec, false)
176+
} else {
177+
to_magnitude_and_suffix(*n as u128, SuffixType::Si, false)
178+
}
179+
}
180+
}
181+
}
153182
}
154183

155184
impl Default for BlockSize {
@@ -196,9 +225,9 @@ impl fmt::Display for BlockSize {
196225
match self {
197226
Self::Bytes(n) => {
198227
let s = if n % 1024 == 0 && n % 1000 != 0 {
199-
to_magnitude_and_suffix(*n as u128, SuffixType::Iec)
228+
to_magnitude_and_suffix(*n as u128, SuffixType::Iec, true)
200229
} else {
201-
to_magnitude_and_suffix(*n as u128, SuffixType::Si)
230+
to_magnitude_and_suffix(*n as u128, SuffixType::Si, true)
202231
};
203232

204233
write!(f, "{s}")
@@ -214,77 +243,168 @@ mod tests {
214243

215244
use crate::blocks::{BlockSize, SuffixType, to_magnitude_and_suffix};
216245

246+
#[test]
247+
fn test_to_magnitude_and_suffix_rounding() {
248+
assert_eq!(
249+
to_magnitude_and_suffix(999_440, SuffixType::Si, true),
250+
"1.0MB"
251+
);
252+
assert_eq!(
253+
to_magnitude_and_suffix(819_200, SuffixType::Si, true),
254+
"820kB"
255+
);
256+
assert_eq!(
257+
to_magnitude_and_suffix(819_936, SuffixType::Si, true),
258+
"820kB"
259+
);
260+
assert_eq!(
261+
to_magnitude_and_suffix(818_400, SuffixType::Si, true),
262+
"819kB"
263+
);
264+
assert_eq!(
265+
to_magnitude_and_suffix(817_600, SuffixType::Si, true),
266+
"818kB"
267+
);
268+
assert_eq!(
269+
to_magnitude_and_suffix(817_200, SuffixType::Si, true),
270+
"818kB"
271+
);
272+
}
273+
274+
#[test]
275+
fn test_to_magnitude_and_suffix_add_tracing_zero() {
276+
assert_eq!(to_magnitude_and_suffix(1024, SuffixType::Iec, true), "1.0K");
277+
assert_eq!(to_magnitude_and_suffix(2048, SuffixType::Iec, true), "2.0K");
278+
assert_eq!(to_magnitude_and_suffix(10240, SuffixType::Iec, true), "10K");
279+
280+
assert_eq!(to_magnitude_and_suffix(1024, SuffixType::Iec, false), "1K");
281+
assert_eq!(to_magnitude_and_suffix(2048, SuffixType::Iec, false), "2K");
282+
assert_eq!(
283+
to_magnitude_and_suffix(10240, SuffixType::Iec, false),
284+
"10K"
285+
);
286+
}
287+
217288
#[test]
218289
fn test_to_magnitude_and_suffix_powers_of_1024() {
219-
assert_eq!(to_magnitude_and_suffix(1024, SuffixType::Iec), "1K");
220-
assert_eq!(to_magnitude_and_suffix(2048, SuffixType::Iec), "2K");
221-
assert_eq!(to_magnitude_and_suffix(4096, SuffixType::Iec), "4K");
222-
assert_eq!(to_magnitude_and_suffix(1024 * 1024, SuffixType::Iec), "1M");
290+
assert_eq!(to_magnitude_and_suffix(1024, SuffixType::Iec, false), "1K");
223291
assert_eq!(
224-
to_magnitude_and_suffix(2 * 1024 * 1024, SuffixType::Iec),
292+
to_magnitude_and_suffix(10240, SuffixType::Iec, false),
293+
"10K"
294+
);
295+
assert_eq!(to_magnitude_and_suffix(2048, SuffixType::Iec, false), "2K");
296+
assert_eq!(
297+
to_magnitude_and_suffix(1024 * 40, SuffixType::Iec, false),
298+
"40K"
299+
);
300+
assert_eq!(
301+
to_magnitude_and_suffix(1024 * 1024, SuffixType::Iec, false),
302+
"1M"
303+
);
304+
assert_eq!(
305+
to_magnitude_and_suffix(2 * 1024 * 1024, SuffixType::Iec, false),
225306
"2M"
226307
);
227308
assert_eq!(
228-
to_magnitude_and_suffix(1024 * 1024 * 1024, SuffixType::Iec),
309+
to_magnitude_and_suffix(1024 * 1024 * 1024, SuffixType::Iec, false),
229310
"1G"
230311
);
231312
assert_eq!(
232-
to_magnitude_and_suffix(34 * 1024 * 1024 * 1024, SuffixType::Iec),
313+
to_magnitude_and_suffix(34 * 1024 * 1024 * 1024, SuffixType::Iec, false),
233314
"34G"
234315
);
235316
}
236317

237318
#[test]
238319
#[allow(clippy::cognitive_complexity)]
239320
fn test_to_magnitude_and_suffix_not_powers_of_1024() {
240-
assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1B");
241-
assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999B");
242-
243-
assert_eq!(to_magnitude_and_suffix(1000, SuffixType::Si), "1kB");
244-
assert_eq!(to_magnitude_and_suffix(1001, SuffixType::Si), "1.1kB");
245-
assert_eq!(to_magnitude_and_suffix(1023, SuffixType::Si), "1.1kB");
246-
assert_eq!(to_magnitude_and_suffix(1025, SuffixType::Si), "1.1kB");
247-
assert_eq!(to_magnitude_and_suffix(10_001, SuffixType::Si), "11kB");
248-
assert_eq!(to_magnitude_and_suffix(999_000, SuffixType::Si), "999kB");
249-
250-
assert_eq!(to_magnitude_and_suffix(999_001, SuffixType::Si), "1MB");
251-
assert_eq!(to_magnitude_and_suffix(999_999, SuffixType::Si), "1MB");
252-
assert_eq!(to_magnitude_and_suffix(1_000_000, SuffixType::Si), "1MB");
253-
assert_eq!(to_magnitude_and_suffix(1_000_001, SuffixType::Si), "1.1MB");
254-
assert_eq!(to_magnitude_and_suffix(1_100_000, SuffixType::Si), "1.1MB");
255-
assert_eq!(to_magnitude_and_suffix(1_100_001, SuffixType::Si), "1.2MB");
256-
assert_eq!(to_magnitude_and_suffix(1_900_000, SuffixType::Si), "1.9MB");
257-
assert_eq!(to_magnitude_and_suffix(1_900_001, SuffixType::Si), "2MB");
258-
assert_eq!(to_magnitude_and_suffix(9_900_000, SuffixType::Si), "9.9MB");
259-
assert_eq!(to_magnitude_and_suffix(9_900_001, SuffixType::Si), "10MB");
321+
assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si, true), "1.0B");
322+
assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si, true), "999B");
323+
324+
assert_eq!(to_magnitude_and_suffix(1000, SuffixType::Si, true), "1.0kB");
325+
assert_eq!(to_magnitude_and_suffix(1001, SuffixType::Si, true), "1.1kB");
326+
assert_eq!(to_magnitude_and_suffix(1023, SuffixType::Si, true), "1.1kB");
327+
assert_eq!(to_magnitude_and_suffix(1025, SuffixType::Si, true), "1.1kB");
328+
assert_eq!(
329+
to_magnitude_and_suffix(10_001, SuffixType::Si, true),
330+
"11kB"
331+
);
332+
assert_eq!(
333+
to_magnitude_and_suffix(999_000, SuffixType::Si, true),
334+
"999kB"
335+
);
336+
337+
assert_eq!(
338+
to_magnitude_and_suffix(999_001, SuffixType::Si, true),
339+
"1.0MB"
340+
);
341+
assert_eq!(
342+
to_magnitude_and_suffix(999_999, SuffixType::Si, true),
343+
"1.0MB"
344+
);
345+
assert_eq!(
346+
to_magnitude_and_suffix(1_000_000, SuffixType::Si, true),
347+
"1.0MB"
348+
);
349+
assert_eq!(
350+
to_magnitude_and_suffix(1_000_001, SuffixType::Si, true),
351+
"1.1MB"
352+
);
353+
assert_eq!(
354+
to_magnitude_and_suffix(1_100_000, SuffixType::Si, true),
355+
"1.1MB"
356+
);
357+
assert_eq!(
358+
to_magnitude_and_suffix(1_100_001, SuffixType::Si, true),
359+
"1.2MB"
360+
);
361+
assert_eq!(
362+
to_magnitude_and_suffix(1_900_000, SuffixType::Si, true),
363+
"1.9MB"
364+
);
260365
assert_eq!(
261-
to_magnitude_and_suffix(999_000_000, SuffixType::Si),
366+
to_magnitude_and_suffix(1_900_001, SuffixType::Si, true),
367+
"2.0MB"
368+
);
369+
assert_eq!(
370+
to_magnitude_and_suffix(9_900_000, SuffixType::Si, true),
371+
"9.9MB"
372+
);
373+
assert_eq!(
374+
to_magnitude_and_suffix(9_900_001, SuffixType::Si, true),
375+
"10MB"
376+
);
377+
assert_eq!(
378+
to_magnitude_and_suffix(999_000_000, SuffixType::Si, true),
262379
"999MB"
263380
);
264381

265-
assert_eq!(to_magnitude_and_suffix(999_000_001, SuffixType::Si), "1GB");
266382
assert_eq!(
267-
to_magnitude_and_suffix(1_000_000_000, SuffixType::Si),
268-
"1GB"
383+
to_magnitude_and_suffix(999_000_001, SuffixType::Si, true),
384+
"1.0GB"
385+
);
386+
assert_eq!(
387+
to_magnitude_and_suffix(1_000_000_000, SuffixType::Si, true),
388+
"1.0GB"
269389
);
270390
assert_eq!(
271-
to_magnitude_and_suffix(1_000_000_001, SuffixType::Si),
391+
to_magnitude_and_suffix(1_000_000_001, SuffixType::Si, true),
272392
"1.1GB"
273393
);
274394
}
275395

276396
#[test]
277397
fn test_block_size_display() {
278-
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K");
279-
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K");
280-
assert_eq!(format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), "3M");
398+
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1.0K");
399+
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2.0K");
400+
assert_eq!(format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), "3.0M");
281401
}
282402

283403
#[test]
284404
fn test_block_size_display_multiples_of_1000_and_1024() {
285405
assert_eq!(format!("{}", BlockSize::Bytes(128_000)), "128kB");
286406
assert_eq!(format!("{}", BlockSize::Bytes(1000 * 1024)), "1.1MB");
287-
assert_eq!(format!("{}", BlockSize::Bytes(1_000_000_000_000)), "1TB");
407+
assert_eq!(format!("{}", BlockSize::Bytes(1_000_000_000_000)), "1.0TB");
288408
}
289409

290410
#[test]

src/uu/df/src/table.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ impl<'a> RowFormatter<'a> {
264264
/// The scaling factor is defined in the `options` field.
265265
fn scaled_bytes(&self, size: u64) -> Cell {
266266
let s = if let Some(h) = self.options.human_readable {
267-
to_magnitude_and_suffix(size.into(), SuffixType::HumanReadable(h))
267+
to_magnitude_and_suffix(size.into(), SuffixType::HumanReadable(h), true)
268268
} else {
269269
let BlockSize::Bytes(d) = self.options.block_size;
270270
(size as f64 / d as f64).ceil().to_string()
@@ -277,7 +277,7 @@ impl<'a> RowFormatter<'a> {
277277
/// The scaling factor is defined in the `options` field.
278278
fn scaled_inodes(&self, size: u128) -> Cell {
279279
let s = if let Some(h) = self.options.human_readable {
280-
to_magnitude_and_suffix(size, SuffixType::HumanReadable(h))
280+
to_magnitude_and_suffix(size, SuffixType::HumanReadable(h), true)
281281
} else {
282282
size.to_string()
283283
};
@@ -377,7 +377,11 @@ impl Header {
377377
translate!("df-blocks-suffix")
378378
)
379379
}
380-
_ => format!("{}{}", options.block_size, translate!("df-blocks-suffix")),
380+
_ => format!(
381+
"{}{}",
382+
options.block_size.to_header(),
383+
translate!("df-blocks-suffix")
384+
),
381385
},
382386
Column::Used => translate!("df-header-used"),
383387
Column::Avail => match options.header_mode {
@@ -822,17 +826,25 @@ mod tests {
822826
fs_type: "my_type".to_string(),
823827
fs_mount: "my_mount".into(),
824828

825-
bytes: 4000,
829+
bytes: 40000,
826830
bytes_used: 1000,
827-
bytes_avail: 3000,
828-
bytes_usage: Some(0.25),
831+
bytes_avail: 39000,
832+
bytes_usage: Some(0.025),
829833

830834
..Default::default()
831835
};
832836
let fmt = RowFormatter::new(&row, &options, false);
833837
assert!(compare_cell_content(
834838
fmt.get_cells(),
835-
vec!("my_device", "my_type", "4k", "1k", "3k", "25%", "my_mount")
839+
vec!(
840+
"my_device",
841+
"my_type",
842+
"40k",
843+
"1.0k",
844+
"39k",
845+
"3%",
846+
"my_mount"
847+
)
836848
));
837849
}
838850

@@ -859,7 +871,15 @@ mod tests {
859871
let fmt = RowFormatter::new(&row, &options, false);
860872
assert!(compare_cell_content(
861873
fmt.get_cells(),
862-
vec!("my_device", "my_type", "4K", "1K", "3K", "25%", "my_mount")
874+
vec!(
875+
"my_device",
876+
"my_type",
877+
"4.0K",
878+
"1.0K",
879+
"3.0K",
880+
"25%",
881+
"my_mount"
882+
)
863883
));
864884
}
865885

tests/by-util/test_df.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,30 @@ fn test_df_follows_symlinks() {
139139
);
140140
}
141141

142+
#[test]
143+
fn test_df_trailing_zeros() {
144+
use regex::Regex;
145+
146+
new_ucmd!()
147+
.arg("-h")
148+
.arg("--output=size,used")
149+
.arg("--total")
150+
.succeeds()
151+
.stdout_does_not_match(&Regex::new("\\s[1-9][A-Z]").unwrap());
152+
}
153+
154+
#[test]
155+
fn test_df_rounding() {
156+
use regex::Regex;
157+
158+
new_ucmd!()
159+
.arg("-H")
160+
.arg("--output=size,used")
161+
.arg("--total")
162+
.succeeds()
163+
.stdout_does_not_match(&Regex::new("\\s\\d{3}\\.\\d[A-Z]").unwrap());
164+
}
165+
142166
#[test]
143167
fn test_df_output_overridden() {
144168
let expected = if cfg!(target_os = "macos") {

0 commit comments

Comments
 (0)