Repository: ouch-org/ouch (language: rust)

Issue:
Progress size unit is inconsistent
### Version

0.4.1

### Description

The following is sculpted from decompression console log.

MB and MiB is mixed.

```
[INFO] ".tmpFVZWxh/OBS/2021-12-24 20-54-05.mp4" extracted. (617.30MiB)
[INFO] ".tmpFVZWxh/OBS/2021-12-24 19-53-20.mp4" extracted. (621.67MiB)
[INFO] ".tmpFVZWxh/OBS/2022-01-07 20-54-27.mp4" extracted. (635.01MB)
[INFO] ".tmpFVZWxh/OBS/2022-01-07 19-53-27.mp4" extracted. (630.09MiB)
[INFO] ".tmpFVZWxh/OBS/2022-01-21 19-54-01.mp4" extracted. (616.31MiB)
[INFO] ".tmpFVZWxh/OBS/2022-01-21 20-52-45.mp4" extracted. (643.32MiB)
[INFO] ".tmpFVZWxh/OBS/2022-02-04 18-53-47.mp4" extracted. (519.68MiB)
[INFO] ".tmpFVZWxh/OBS/2022-02-04 19-53-34.mp4" extracted. (632.81MiB)
[INFO] ".tmpFVZWxh/OBS/2022-02-18 19-53-25.mp4" extracted. (515.16MiB)
[INFO] ".tmpFVZWxh/OBS/2022-02-18 17-22-44.mp4" extracted. (540.20MiB)
[INFO] ".tmpFVZWxh/OBS/2022-02-18 18-53-49.mp4" extracted. (620.21MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-11 17-28-49.mp4" extracted. (535.01MB)
[INFO] ".tmpFVZWxh/OBS/2022-03-11 19-52-43.mp4" extracted. (539.44MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-11 18-53-08.mp4" extracted. (644.84MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-11 18-29-09.mp4" extracted. (22.77MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-18 16-31-23.mp4" extracted. (53.40MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-18 16-19-56.mp4" extracted. (70.79MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-18 16-23-10.mp4" extracted. (79.09MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-18 16-26-43.mp4" extracted. (98.95MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-18 16-14-20.mp4" extracted. (123.33MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-25 16-52-20.mp4" extracted. (439.51MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-25 16-19-09.mp4" extracted. (496.33MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-25 18-52-07.mp4" extracted. (665.34MiB)
[INFO] ".tmpFVZWxh/OBS/2022-03-18 18-03-46.mp4" extracted. (867.00MB)
[INFO] ".tmpFVZWxh/OBS/2022-03-18 18-43-09.mp4" extracted. (843.60MiB)
```



### Current Behavior

MB and MiB is mixed.

### Expected Behavior

All MiB or MB.

### Additional Information

_No response_


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index e4fa540c4..497ff5a55 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -735,7 +735,6 @@ dependencies = [
  "tempfile",
  "test-strategy",
  "time",
- "ubyte",
  "xz2",
  "zip",
  "zstd",
@@ -1188,12 +1187,6 @@ dependencies = [
  "static_assertions",
 ]
 
-[[package]]
-name = "ubyte"
-version = "0.10.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6"
-
 [[package]]
 name = "unarray"
 version = "0.1.4"
diff --git a/Cargo.toml b/Cargo.toml
index a15de742c..f87207a15 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,7 +30,6 @@ snap = "1.1.0"
 tar = "0.4.40"
 tempfile = "3.8.0"
 time = { version = "0.3.28", default-features = false }
-ubyte = { version = "0.10.3", default-features = false }
 xz2 = "0.1.7"
 zip = { version = "0.6.6", default-features = false, features = ["time"] }
 zstd = { version = "0.12.4", default-features = false }
diff --git a/src/archive/tar.rs b/src/archive/tar.rs
index 54f144f95..d37628819 100644
--- a/src/archive/tar.rs
+++ b/src/archive/tar.rs
@@ -10,13 +10,12 @@ use std::{
 
 use fs_err as fs;
 use same_file::Handle;
-use ubyte::ToByteUnit;
 
 use crate::{
     error::FinalError,
     info,
     list::FileInArchive,
-    utils::{self, EscapedPathDisplay, FileVisibilityPolicy},
+    utils::{self, Bytes, EscapedPathDisplay, FileVisibilityPolicy},
     warning,
 };
 
@@ -41,7 +40,7 @@ pub fn unpack_archive(reader: Box<dyn Read>, output_folder: &Path, quiet: bool)
                 inaccessible,
                 "{:?} extracted. ({})",
                 utils::strip_cur_dir(&output_folder.join(file.path()?)),
-                file.size().bytes(),
+                Bytes::new(file.size()),
             );
 
             files_unpacked += 1;
diff --git a/src/archive/zip.rs b/src/archive/zip.rs
index 47d73637b..60cc440db 100644
--- a/src/archive/zip.rs
+++ b/src/archive/zip.rs
@@ -14,7 +14,6 @@ use filetime::{set_file_mtime, FileTime};
 use fs_err as fs;
 use same_file::Handle;
 use time::OffsetDateTime;
-use ubyte::ToByteUnit;
 use zip::{self, read::ZipFile, DateTime, ZipArchive};
 
 use crate::{
@@ -22,7 +21,7 @@ use crate::{
     info,
     list::FileInArchive,
     utils::{
-        self, cd_into_same_dir_as, get_invalid_utf8_paths, pretty_format_list_of_paths, strip_cur_dir,
+        self, cd_into_same_dir_as, get_invalid_utf8_paths, pretty_format_list_of_paths, strip_cur_dir, Bytes,
         EscapedPathDisplay, FileVisibilityPolicy,
     },
     warning,
@@ -74,7 +73,7 @@ where
                         inaccessible,
                         "{:?} extracted. ({})",
                         file_path.display(),
-                        file.size().bytes()
+                        Bytes::new(file.size()),
                     );
                 }
 
diff --git a/src/utils/formatting.rs b/src/utils/formatting.rs
index 7a409e5e9..fac5c1126 100644
--- a/src/utils/formatting.rs
+++ b/src/utils/formatting.rs
@@ -1,4 +1,4 @@
-use std::{borrow::Cow, fmt::Display, path::Path};
+use std::{borrow::Cow, cmp, fmt::Display, path::Path};
 
 use crate::CURRENT_DIRECTORY;
 
@@ -86,3 +86,81 @@ pub fn nice_directory_display(path: &Path) -> Cow<str> {
         to_utf(path)
     }
 }
+
+/// Struct useful to printing bytes as kB, MB, GB, etc.
+pub struct Bytes(f64);
+
+impl Bytes {
+    const UNIT_PREFIXES: [&'static str; 6] = ["", "ki", "Mi", "Gi", "Ti", "Pi"];
+
+    /// Create a new Bytes.
+    pub fn new(bytes: u64) -> Self {
+        Self(bytes as f64)
+    }
+}
+
+impl std::fmt::Display for Bytes {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let &Self(num) = self;
+
+        debug_assert!(num >= 0.0);
+        if num < 1_f64 {
+            return write!(f, "{} B", num);
+        }
+
+        let delimiter = 1000_f64;
+        let exponent = cmp::min((num.ln() / 6.90775).floor() as i32, 4);
+
+        write!(
+            f,
+            "{:.2} {}B",
+            num / delimiter.powi(exponent),
+            Bytes::UNIT_PREFIXES[exponent as usize]
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_pretty_bytes_formatting() {
+        fn format_bytes(bytes: u64) -> String {
+            format!("{}", Bytes::new(bytes))
+        }
+        let b = 1;
+        let kb = b * 1000;
+        let mb = kb * 1000;
+        let gb = mb * 1000;
+
+        assert_eq!("0 B", format_bytes(0)); // This is weird
+        assert_eq!("1.00 B", format_bytes(b));
+        assert_eq!("999.00 B", format_bytes(b * 999));
+        assert_eq!("12.00 MiB", format_bytes(mb * 12));
+        assert_eq!("123.00 MiB", format_bytes(mb * 123));
+        assert_eq!("5.50 MiB", format_bytes(mb * 5 + kb * 500));
+        assert_eq!("7.54 GiB", format_bytes(gb * 7 + 540 * mb));
+        assert_eq!("1.20 TiB", format_bytes(gb * 1200));
+
+        // bytes
+        assert_eq!("234.00 B", format_bytes(234));
+        assert_eq!("999.00 B", format_bytes(999));
+        // kilobytes
+        assert_eq!("2.23 kiB", format_bytes(2234));
+        assert_eq!("62.50 kiB", format_bytes(62500));
+        assert_eq!("329.99 kiB", format_bytes(329990));
+        // megabytes
+        assert_eq!("2.75 MiB", format_bytes(2750000));
+        assert_eq!("55.00 MiB", format_bytes(55000000));
+        assert_eq!("987.65 MiB", format_bytes(987654321));
+        // gigabytes
+        assert_eq!("5.28 GiB", format_bytes(5280000000));
+        assert_eq!("95.20 GiB", format_bytes(95200000000));
+        assert_eq!("302.00 GiB", format_bytes(302000000000));
+        assert_eq!("302.99 GiB", format_bytes(302990000000));
+        // Weird aproximation cases:
+        assert_eq!("999.90 GiB", format_bytes(999900000000));
+        assert_eq!("1.00 TiB", format_bytes(999990000000));
+    }
+}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index a601287c7..3487b0cf2 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -10,7 +10,9 @@ mod fs;
 mod question;
 
 pub use file_visibility::FileVisibilityPolicy;
-pub use formatting::{nice_directory_display, pretty_format_list_of_paths, strip_cur_dir, to_utf, EscapedPathDisplay};
+pub use formatting::{
+    nice_directory_display, pretty_format_list_of_paths, strip_cur_dir, to_utf, Bytes, EscapedPathDisplay,
+};
 pub use fs::{
     cd_into_same_dir_as, clear_path, create_dir_if_non_existent, is_symlink, remove_file_or_dir, try_infer_extension,
 };


Test patch: