use std::{
borrow::Cow,
cmp,
ffi::{OsStr, OsString},
fmt::{self, Write as _},
path::{Path, PathBuf},
};
use crate::INITIAL_CURRENT_DIR;
pub fn path_to_str(path: &Path) -> Cow<'_, str> {
os_str_to_str(path.as_ref())
}
pub fn os_str_to_str(os_str: &OsStr) -> Cow<'_, str> {
let format = || {
let text = format!("{os_str:?}");
Cow::Owned(text.trim_matches('"').to_string())
};
os_str.to_str().map_or_else(format, Cow::Borrowed)
}
pub fn strip_cur_dir(source_path: &Path) -> &Path {
source_path.strip_prefix(&*INITIAL_CURRENT_DIR).unwrap_or(source_path)
}
pub fn pretty_format_list_of_paths(paths: &[impl AsRef<Path>]) -> String {
let mut string = String::new();
for (i, path) in paths.iter().enumerate() {
if i != 0 {
string += ", ";
}
write!(string, "{}", PathFmt(path.as_ref())).expect("Couldn't write to a string");
}
string
}
pub fn nice_directory_display(path: &Path) -> Cow<'_, str> {
if path == Path::new(".") {
Cow::Borrowed("current directory")
} else {
path_to_str(path)
}
}
pub fn strip_path_ascii_prefix<'a>(path: Cow<'a, Path>, ascii_prefix: &str) -> Cow<'a, Path> {
assert!(ascii_prefix.is_ascii());
let prefix_slice = ascii_prefix.as_bytes();
let path_slice = path.as_os_str().as_encoded_bytes();
if let Some(stripped) = path_slice.strip_prefix(prefix_slice) {
let str = unsafe { OsStr::from_encoded_bytes_unchecked(stripped) };
Cow::from(PathBuf::from(str))
} else {
path
}
}
pub fn append_ascii_suffix_to_os_str(os_str: &OsStr, ascii_suffix: &str) -> OsString {
assert!(ascii_suffix.is_ascii());
let mut bytes = os_str.as_encoded_bytes().to_vec();
bytes.extend_from_slice(ascii_suffix.as_bytes());
unsafe { OsStr::from_encoded_bytes_unchecked(&bytes) }.to_owned()
}
pub struct PathFmt<'a>(pub &'a Path);
pub struct NoQuotePathFmt<'a>(pub &'a Path);
impl<'a> fmt::Display for PathFmt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", NoQuotePathFmt(self.0))
}
}
impl<'a> fmt::Display for NoQuotePathFmt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let path = self.0;
debug_assert_ne!(path.as_os_str().as_encoded_bytes().len(), 0, "empty path");
let path = strip_path_ascii_prefix(Cow::Borrowed(path), "./");
let path = path.as_ref();
let path = path.strip_prefix(&*INITIAL_CURRENT_DIR).unwrap_or(path);
let path = if path.as_os_str().is_empty() {
Path::new(".")
} else {
path
};
write!(f, "{}", path.display())
}
}
pub struct BytesFmt(pub u64);
impl BytesFmt {
const UNIT_PREFIXES: [&'static str; 6] = ["", "ki", "Mi", "Gi", "Ti", "Pi"];
}
impl fmt::Display for BytesFmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let num = self.0 as f64;
debug_assert!(num >= 0.0);
if num < 1_f64 {
return write!(f, "{num:>6.2} B");
}
let delimiter = 1000_f64;
let exponent = cmp::min((num.ln() / 6.90775).floor() as i32, 4);
write!(
f,
"{:>6.2} {:>2}B",
num / delimiter.powi(exponent),
Self::UNIT_PREFIXES[exponent as usize],
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pretty_bytes_formatting() {
fn format_bytes(bytes: u64) -> String {
format!("{}", BytesFmt(bytes))
}
let b = 1;
let kb = b * 1000;
let mb = kb * 1000;
let gb = mb * 1000;
assert_eq!(" 0.00 B", format_bytes(0)); 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));
assert_eq!("234.00 B", format_bytes(234));
assert_eq!("999.00 B", format_bytes(999));
assert_eq!(" 2.23 kiB", format_bytes(2234));
assert_eq!(" 62.50 kiB", format_bytes(62500));
assert_eq!("329.99 kiB", format_bytes(329990));
assert_eq!(" 2.75 MiB", format_bytes(2750000));
assert_eq!(" 55.00 MiB", format_bytes(55000000));
assert_eq!("987.65 MiB", format_bytes(987654321));
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));
assert_eq!("999.90 GiB", format_bytes(999900000000));
assert_eq!(" 1.00 TiB", format_bytes(999990000000));
}
}