use crate::{
cli::NumberSeparator,
info::utils::{format_number, info_field::InfoField},
};
use byte_unit::{Byte, UnitType};
use gix::Repository;
use serde::Serialize;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SizeInfo {
pub repo_size: String,
pub file_count: u64,
#[serde(skip_serializing)]
number_separator: NumberSeparator,
}
impl SizeInfo {
pub fn new(repo: &Repository, number_separator: NumberSeparator) -> Self {
let (repo_size, file_count) = get_repo_size(repo);
Self {
repo_size,
file_count,
number_separator,
}
}
}
fn get_repo_size(repo: &Repository) -> (String, u64) {
let (repo_size, file_count) = match repo.index() {
Ok(index) => {
let repo_size = index.entries().iter().map(|e| e.stat.size as u64).sum();
(repo_size, index.entries().len() as u64)
}
_ => (0, 0),
};
(bytes_to_human_readable(repo_size), file_count)
}
fn bytes_to_human_readable(bytes: u64) -> String {
let byte = Byte::from_u64(bytes);
let adjusted_byte_based = byte.get_appropriate_unit(UnitType::Binary);
format!("{adjusted_byte_based:#.2}")
}
impl std::fmt::Display for SizeInfo {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.file_count {
0 => write!(f, "{}", &self.repo_size),
1 => write!(f, "{} (1 file)", self.repo_size),
_ => {
write!(
f,
"{} ({} files)",
self.repo_size,
format_number(&self.file_count, self.number_separator)
)
}
}
}
}
#[typetag::serialize]
impl InfoField for SizeInfo {
fn value(&self) -> String {
self.to_string()
}
fn title(&self) -> String {
"Size".into()
}
}
#[cfg(test)]
mod test {
use rstest::rstest;
use super::*;
#[test]
fn test_display_size_info() {
let size_info = SizeInfo {
repo_size: "2.40 MiB".to_string(),
file_count: 123,
number_separator: NumberSeparator::Plain,
};
assert_eq!(size_info.value(), "2.40 MiB (123 files)".to_string());
}
#[test]
fn test_display_size_info_no_files() {
let size_info = SizeInfo {
repo_size: "2.40 MiB".to_string(),
file_count: 0,
number_separator: NumberSeparator::Plain,
};
assert_eq!(size_info.value(), "2.40 MiB".to_string());
}
#[test]
fn test_display_size_info_one_files() {
let size_info = SizeInfo {
repo_size: "2.40 MiB".to_string(),
file_count: 1,
number_separator: NumberSeparator::Plain,
};
assert_eq!(size_info.value(), "2.40 MiB (1 file)".to_string());
}
#[rstest(
case(0, "0 B"),
case(1023, "1023 B"),
case(1024, "1 KiB"),
case(2048, "2 KiB"),
case(1048576, "1 MiB"),
case(1099511627776, "1 TiB"),
case(2577152, "2.46 MiB")
)]
fn test_bytes_to_human_readable(#[case] input: u64, #[case] expected: &str) {
assert_eq!(bytes_to_human_readable(input), expected);
}
}