From 101e64d35eb122669ed02a15dd0d960d19e25678 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Thu, 18 May 2023 00:14:17 -0700 Subject: [PATCH 1/7] beginning of file size refactor --- src/disk_usage/block.rs | 0 src/disk_usage/file_size.rs | 40 +++++++++---------- src/disk_usage/logical.rs | 77 +++++++++++++++++++++++++++++++++++++ src/disk_usage/mod.rs | 50 +++++++++++++++++++++++- src/disk_usage/physical.rs | 68 ++++++++++++++++++++++++++++++++ src/tree/mod.rs | 2 +- src/tree/node/cmp.rs | 4 +- 7 files changed, 217 insertions(+), 24 deletions(-) create mode 100644 src/disk_usage/block.rs create mode 100644 src/disk_usage/logical.rs create mode 100644 src/disk_usage/physical.rs diff --git a/src/disk_usage/block.rs b/src/disk_usage/block.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/disk_usage/file_size.rs b/src/disk_usage/file_size.rs index 7305e8ed..d7a1efb8 100644 --- a/src/disk_usage/file_size.rs +++ b/src/disk_usage/file_size.rs @@ -11,7 +11,7 @@ use std::{borrow::Cow, fs::Metadata, ops::AddAssign, path::Path}; /// Represents either logical or physical size and handles presentation. #[derive(Clone, Debug)] pub struct FileSize { - pub bytes: u64, + pub value: u64, #[allow(dead_code)] disk_usage: DiskUsage, prefix_kind: PrefixKind, @@ -34,7 +34,7 @@ pub enum DiskUsage { /// How many bytes does a file contain Logical, - /// How much actual space on disk, taking into account sparse files and compression. + /// How much actual space on disk in bytes, taking into account sparse files and compression. #[default] Physical, } @@ -42,13 +42,13 @@ pub enum DiskUsage { impl FileSize { /// Initializes a [`FileSize`]. pub const fn new( - bytes: u64, + value: u64, disk_usage: DiskUsage, human_readable: bool, prefix_kind: PrefixKind, ) -> Self { Self { - bytes, + value, disk_usage, human_readable, prefix_kind, @@ -84,22 +84,22 @@ impl FileSize { /// Precompute the raw (unpadded) display and sets the number of columns the size (without /// the prefix) will occupy. Also sets the [Style] to use in advance to style the size output. pub fn precompute_unpadded_display(&mut self) { - let fbytes = self.bytes as f64; + let value = self.value as f64; match self.prefix_kind { PrefixKind::Si => { - let unit = SiPrefix::from(fbytes); + let unit = SiPrefix::from(value); let base_value = unit.base_value(); if !self.human_readable { - self.unpadded_display = Some(format!("{} B", self.bytes)); - self.size_columns = utils::num_integral(self.bytes); + self.unpadded_display = Some(format!("{} B", self.value)); + self.size_columns = utils::num_integral(self.value); } else if matches!(unit, SiPrefix::Base) { - self.unpadded_display = Some(format!("{} {unit}", self.bytes)); - self.size_columns = utils::num_integral(self.bytes); + self.unpadded_display = Some(format!("{} {unit}", self.value)); + self.size_columns = utils::num_integral(self.value); self.uses_base_unit = Some(()); } else { - let size = fbytes / (base_value as f64); + let size = value / (base_value as f64); self.unpadded_display = Some(format!("{size:.2} {unit}")); self.size_columns = utils::num_integral((size * 100.0).floor() as u64) + 1; } @@ -110,18 +110,18 @@ impl FileSize { } } PrefixKind::Bin => { - let unit = BinPrefix::from(fbytes); + let unit = BinPrefix::from(value); let base_value = unit.base_value(); if !self.human_readable { - self.unpadded_display = Some(format!("{} B", self.bytes)); - self.size_columns = utils::num_integral(self.bytes); + self.unpadded_display = Some(format!("{} B", self.value)); + self.size_columns = utils::num_integral(self.value); } else if matches!(unit, BinPrefix::Base) { - self.unpadded_display = Some(format!("{} {unit}", self.bytes)); - self.size_columns = utils::num_integral(self.bytes); + self.unpadded_display = Some(format!("{} {unit}", self.value)); + self.size_columns = utils::num_integral(self.value); self.uses_base_unit = Some(()); } else { - let size = fbytes / (base_value as f64); + let size = value / (base_value as f64); self.unpadded_display = Some(format!("{size:.2} {unit}")); self.size_columns = utils::num_integral((size * 100.0).floor() as u64) + 1; } @@ -144,13 +144,13 @@ impl FileSize { if self.uses_base_unit.is_some() { format!( "{:>max_size_width$} {unit:>max_size_unit_width$}", - self.bytes + self.value ) } else { format!("{size:>max_size_width$} {unit:>max_size_unit_width$}") } } else { - format!("{: for FileSize { fn add_assign(&mut self, rhs: &Self) { - self.bytes += rhs.bytes; + self.value += rhs.value; } } diff --git a/src/disk_usage/logical.rs b/src/disk_usage/logical.rs new file mode 100644 index 00000000..6818cd49 --- /dev/null +++ b/src/disk_usage/logical.rs @@ -0,0 +1,77 @@ +use super::{ + ByteDisplay, + DuMetric, + units::PrefixKind, +}; +use std::{ + fmt::{self, Display}, + fs::Metadata, +}; + +pub struct Size { + pub value: u64, + pub prefix_kind: PrefixKind, + pub human_readable: bool, +} + +impl DuMetric for Size {} + +impl Size { + pub fn new(metadata: &Metadata, prefix_kind: PrefixKind, human_readable: bool) -> Self { + let value = metadata.len(); + + Self { + value, + prefix_kind, + human_readable, + } + } +} + +impl ByteDisplay for Size { + fn human_readable(&self) -> bool { + self.human_readable + } + + fn prefix_kind(&self) -> PrefixKind { + self.prefix_kind + } + + fn value(&self) -> u64 { + self.value + } +} + +impl Display for Size { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } +} + +#[test] +fn test_logical_size() -> std::io::Result<()> { + use std::fs; + + let md = fs::metadata("./tests/data/nemesis.txt")?; + + let logical_size = Size::new(&md, PrefixKind::Bin, false); + + let display = format!("{logical_size}"); + + assert_eq!(logical_size.value, 161); + assert_eq!(display, "161 B"); + + assert_eq!( + format!( + "{}", + Size { + value: 1_024, + prefix_kind: PrefixKind::Bin, + human_readable: true + } + ), + "1.00 KiB" + ); + + Ok(()) +} diff --git a/src/disk_usage/mod.rs b/src/disk_usage/mod.rs index 5c325e2e..ba94ac2e 100644 --- a/src/disk_usage/mod.rs +++ b/src/disk_usage/mod.rs @@ -1,5 +1,53 @@ +use std::fmt; +use units::{BinPrefix, PrefixKind, SiPrefix, UnitPrefix}; + /// Binary and SI prefixes pub mod units; -/// Rules to display disk usage for individual files +/// Rules to display disk usage for individual files. pub mod file_size; + +pub mod block; + +pub mod logical; + +pub mod physical; + +pub trait DuMetric {} + +pub trait ByteDisplay { + fn prefix_kind(&self) -> PrefixKind; + + fn value(&self) -> u64; + + fn human_readable(&self) -> bool; + + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = self.value() as f64; + + match self.prefix_kind() { + PrefixKind::Si => { + let unit = SiPrefix::from(value); + let base_value = unit.base_value(); + + if !self.human_readable() || matches!(unit, SiPrefix::Base) { + write!(f, "{} {unit}", self.value()) + } else { + let size = value / (base_value as f64); + write!(f, "{size:.2} {unit}") + } + } + PrefixKind::Bin => { + let unit = BinPrefix::from(value); + let base_value = unit.base_value(); + + if !self.human_readable() || matches!(unit, BinPrefix::Base) { + write!(f, "{} {unit}", self.value()) + } else { + let size = value / (base_value as f64); + write!(f, "{size:.2} {unit}") + } + } + } + } +} diff --git a/src/disk_usage/physical.rs b/src/disk_usage/physical.rs new file mode 100644 index 00000000..d31174bf --- /dev/null +++ b/src/disk_usage/physical.rs @@ -0,0 +1,68 @@ +use filesize::PathExt; +use super::{ + units::PrefixKind, + ByteDisplay, + DuMetric, +}; +use std::{ + path::Path, + fmt::{self, Display}, + fs::Metadata, +}; + +pub struct Size { + pub value: u64, + pub prefix_kind: PrefixKind, + pub human_readable: bool, +} + +impl DuMetric for Size {} + +impl Size { + pub fn new(path: &Path, metadata: &Metadata, prefix_kind: PrefixKind, human_readable: bool) -> Self { + let value = path.size_on_disk_fast(metadata).unwrap_or(0); + + Self { + value, + prefix_kind, + human_readable, + } + } +} + +impl ByteDisplay for Size { + fn human_readable(&self) -> bool { + self.human_readable + } + + fn prefix_kind(&self) -> PrefixKind { + self.prefix_kind + } + + fn value(&self) -> u64 { + self.value + } +} + +impl Display for Size { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } +} + +#[test] +fn test_physical_size() -> std::io::Result<()> { + assert_eq!( + format!( + "{}", + Size { + value: 1_024, + prefix_kind: PrefixKind::Bin, + human_readable: true + } + ), + "1.00 KiB" + ); + + Ok(()) +} diff --git a/src/tree/mod.rs b/src/tree/mod.rs index e5d4939c..02a99ae5 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -228,7 +228,7 @@ impl Tree { } } - if dir_size.bytes > 0 { + if dir_size.value > 0 { let dir = tree[current_node_id].get_mut(); dir_size.precompute_unpadded_display(); diff --git a/src/tree/node/cmp.rs b/src/tree/node/cmp.rs index d9e371dd..78613f73 100644 --- a/src/tree/node/cmp.rs +++ b/src/tree/node/cmp.rs @@ -132,8 +132,8 @@ mod sizing { /// Comparator that sorts [Node]s by size, largest to smallest. pub fn comparator(a: &Node, b: &Node) -> Ordering { - let a_size = a.file_size().map_or(0, |fs| fs.bytes); - let b_size = b.file_size().map_or(0, |fs| fs.bytes); + let a_size = a.file_size().map_or(0, |fs| fs.value); + let b_size = b.file_size().map_or(0, |fs| fs.value); b_size.cmp(&a_size) } /// Comparator that sorts [Node]s by size, smallest to largest. From a1abab5f013dcc8ca416d8eb50f54fd386a8b6bb Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sun, 21 May 2023 21:07:53 -0700 Subject: [PATCH 2/7] refactor file size --- src/context/output.rs | 5 +- src/disk_usage/block.rs | 0 src/disk_usage/file_size.rs | 191 ------------------------- src/disk_usage/file_size/block.rs | 3 + src/disk_usage/file_size/byte.rs | 163 +++++++++++++++++++++ src/disk_usage/file_size/line_count.rs | 3 + src/disk_usage/file_size/mod.rs | 51 +++++++ src/disk_usage/file_size/word_count.rs | 3 + src/disk_usage/logical.rs | 77 ---------- src/disk_usage/mod.rs | 49 ------- src/disk_usage/physical.rs | 68 --------- src/disk_usage/units.rs | 12 +- src/icons/fs.rs | 95 ++++++++++++ src/{icons.rs => icons/mod.rs} | 97 +------------ src/render/grid/cell.rs | 54 ++++++- src/render/theme.rs | 3 +- src/tree/mod.rs | 61 ++++++-- src/tree/node/cmp.rs | 5 +- src/tree/node/mod.rs | 19 ++- tests/dirs_only.rs | 10 +- tests/flat.rs | 58 ++++---- tests/glob.rs | 70 ++++----- tests/hardlink.rs | 6 +- tests/level.rs | 16 +-- tests/prune.rs | 18 +-- tests/regex.rs | 34 ++--- tests/sort.rs | 88 ++++++------ tests/suppress_size.rs | 20 +-- 28 files changed, 599 insertions(+), 680 deletions(-) delete mode 100644 src/disk_usage/block.rs delete mode 100644 src/disk_usage/file_size.rs create mode 100644 src/disk_usage/file_size/block.rs create mode 100644 src/disk_usage/file_size/byte.rs create mode 100644 src/disk_usage/file_size/line_count.rs create mode 100644 src/disk_usage/file_size/mod.rs create mode 100644 src/disk_usage/file_size/word_count.rs delete mode 100644 src/disk_usage/logical.rs delete mode 100644 src/disk_usage/physical.rs create mode 100644 src/icons/fs.rs rename src/{icons.rs => icons/mod.rs} (88%) diff --git a/src/context/output.rs b/src/context/output.rs index 8b7582b2..79f6c680 100644 --- a/src/context/output.rs +++ b/src/context/output.rs @@ -20,8 +20,9 @@ pub struct ColumnProperties { impl From<&Context> for ColumnProperties { fn from(ctx: &Context) -> Self { let unit_width = match ctx.unit { - PrefixKind::Si => 2, - PrefixKind::Bin => 3, + PrefixKind::Bin if ctx.human => 3, + PrefixKind::Si if ctx.human => 2, + _ => 1, }; Self { diff --git a/src/disk_usage/block.rs b/src/disk_usage/block.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/disk_usage/file_size.rs b/src/disk_usage/file_size.rs deleted file mode 100644 index d7a1efb8..00000000 --- a/src/disk_usage/file_size.rs +++ /dev/null @@ -1,191 +0,0 @@ -use super::units::{BinPrefix, PrefixKind, SiPrefix, UnitPrefix}; -use crate::{ - styles::{self, get_du_theme, get_placeholder_style}, - utils, Context, -}; -use ansi_term::Style; -use clap::ValueEnum; -use filesize::PathExt; -use std::{borrow::Cow, fs::Metadata, ops::AddAssign, path::Path}; - -/// Represents either logical or physical size and handles presentation. -#[derive(Clone, Debug)] -pub struct FileSize { - pub value: u64, - #[allow(dead_code)] - disk_usage: DiskUsage, - prefix_kind: PrefixKind, - human_readable: bool, - unpadded_display: Option, - - // Precomputed style to use - style: Option<&'static Style>, - - // Does this file size use `B` without a prefix? - uses_base_unit: Option<()>, - - // How many columns are required for the size (without prefix). - pub size_columns: usize, -} - -/// Determines between logical or physical size for display -#[derive(Copy, Clone, Debug, ValueEnum, Default)] -pub enum DiskUsage { - /// How many bytes does a file contain - Logical, - - /// How much actual space on disk in bytes, taking into account sparse files and compression. - #[default] - Physical, -} - -impl FileSize { - /// Initializes a [`FileSize`]. - pub const fn new( - value: u64, - disk_usage: DiskUsage, - human_readable: bool, - prefix_kind: PrefixKind, - ) -> Self { - Self { - value, - disk_usage, - human_readable, - prefix_kind, - unpadded_display: None, - style: None, - uses_base_unit: None, - size_columns: 0, - } - } - - /// Computes the logical size of a file given its [Metadata]. - pub fn logical(md: &Metadata, prefix_kind: PrefixKind, human_readable: bool) -> Self { - let bytes = md.len(); - Self::new(bytes, DiskUsage::Logical, human_readable, prefix_kind) - } - - /// Computes the physical size of a file given its [Path] and [Metadata]. - pub fn physical( - path: &Path, - md: &Metadata, - prefix_kind: PrefixKind, - human_readable: bool, - ) -> Option { - path.size_on_disk_fast(md) - .ok() - .map(|bytes| Self::new(bytes, DiskUsage::Physical, human_readable, prefix_kind)) - } - - pub fn unpadded_display(&self) -> Option<&str> { - self.unpadded_display.as_deref() - } - - /// Precompute the raw (unpadded) display and sets the number of columns the size (without - /// the prefix) will occupy. Also sets the [Style] to use in advance to style the size output. - pub fn precompute_unpadded_display(&mut self) { - let value = self.value as f64; - - match self.prefix_kind { - PrefixKind::Si => { - let unit = SiPrefix::from(value); - let base_value = unit.base_value(); - - if !self.human_readable { - self.unpadded_display = Some(format!("{} B", self.value)); - self.size_columns = utils::num_integral(self.value); - } else if matches!(unit, SiPrefix::Base) { - self.unpadded_display = Some(format!("{} {unit}", self.value)); - self.size_columns = utils::num_integral(self.value); - self.uses_base_unit = Some(()); - } else { - let size = value / (base_value as f64); - self.unpadded_display = Some(format!("{size:.2} {unit}")); - self.size_columns = utils::num_integral((size * 100.0).floor() as u64) + 1; - } - - if let Ok(theme) = get_du_theme() { - let style = theme.get(format!("{unit}").as_str()); - self.style = style; - } - } - PrefixKind::Bin => { - let unit = BinPrefix::from(value); - let base_value = unit.base_value(); - - if !self.human_readable { - self.unpadded_display = Some(format!("{} B", self.value)); - self.size_columns = utils::num_integral(self.value); - } else if matches!(unit, BinPrefix::Base) { - self.unpadded_display = Some(format!("{} {unit}", self.value)); - self.size_columns = utils::num_integral(self.value); - self.uses_base_unit = Some(()); - } else { - let size = value / (base_value as f64); - self.unpadded_display = Some(format!("{size:.2} {unit}")); - self.size_columns = utils::num_integral((size * 100.0).floor() as u64) + 1; - } - - if let Ok(theme) = get_du_theme() { - let style = theme.get(format!("{unit}").as_str()); - self.style = style; - } - } - } - } - - /// Formats [`FileSize`] for presentation. - pub fn format(&self, max_size_width: usize, max_size_unit_width: usize) -> String { - let out = if self.human_readable { - let mut precomputed = self.unpadded_display().unwrap().split(' '); - let size = precomputed.next().unwrap(); - let unit = precomputed.next().unwrap(); - - if self.uses_base_unit.is_some() { - format!( - "{:>max_size_width$} {unit:>max_size_unit_width$}", - self.value - ) - } else { - format!("{size:>max_size_width$} {unit:>max_size_unit_width$}") - } - } else { - format!("{: String { - if ctx.suppress_size || ctx.max_size_width == 0 { - return String::new(); - } - - let placeholder = get_placeholder_style().map_or_else( - |_| Cow::from(styles::PLACEHOLDER), - |style| Cow::from(style.paint(styles::PLACEHOLDER).to_string()), - ); - - let placeholder_padding = placeholder.len() - + ctx.max_size_width - + match ctx.unit { - PrefixKind::Si if ctx.human => 2, - PrefixKind::Bin if ctx.human => 3, - PrefixKind::Si => 0, - PrefixKind::Bin => 1, - }; - - format!("{placeholder:>placeholder_padding$}") - } -} - -impl AddAssign<&Self> for FileSize { - fn add_assign(&mut self, rhs: &Self) { - self.value += rhs.value; - } -} diff --git a/src/disk_usage/file_size/block.rs b/src/disk_usage/file_size/block.rs new file mode 100644 index 00000000..f845daeb --- /dev/null +++ b/src/disk_usage/file_size/block.rs @@ -0,0 +1,3 @@ +pub struct Metric { + value: u64, +} diff --git a/src/disk_usage/file_size/byte.rs b/src/disk_usage/file_size/byte.rs new file mode 100644 index 00000000..bff25dd1 --- /dev/null +++ b/src/disk_usage/file_size/byte.rs @@ -0,0 +1,163 @@ +use super::super::units::{BinPrefix, PrefixKind, SiPrefix, UnitPrefix}; +use crate::context::Context; +use filesize::PathExt; +use std::{ + convert::From, + fmt::{self, Display}, + fs::Metadata, + path::Path, +}; + +pub struct Metric { + pub value: u64, + pub human_readable: bool, + #[allow(dead_code)] + kind: MetricKind, + prefix_kind: PrefixKind, +} + +pub enum MetricKind { + Logical, + Physical, +} + +impl Metric { + pub fn init_logical( + metadata: &Metadata, + prefix_kind: PrefixKind, + human_readable: bool, + ) -> Self { + let value = metadata.len(); + let kind = MetricKind::Logical; + + Self { + value, + human_readable, + kind, + prefix_kind, + } + } + + pub fn init_physical( + path: &Path, + metadata: &Metadata, + prefix_kind: PrefixKind, + human_readable: bool, + ) -> Self { + let value = path.size_on_disk_fast(metadata).unwrap_or(metadata.len()); + let kind = MetricKind::Physical; + + Self { + value, + human_readable, + kind, + prefix_kind, + } + } +} + +impl From<&Context> for Metric { + fn from(ctx: &Context) -> Self { + let metric_kind = match ctx.disk_usage { + super::DiskUsage::Logical => MetricKind::Logical, + super::DiskUsage::Physical => MetricKind::Physical, + }; + + Self { + value: 0, + prefix_kind: ctx.unit, + human_readable: ctx.human, + kind: metric_kind, + } + } +} + +impl Display for Metric { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = self.value as f64; + + match self.prefix_kind { + PrefixKind::Si => { + if !self.human_readable { + return write!(f, "{} {}", self.value, SiPrefix::Base); + } + + let unit = SiPrefix::from(self.value); + + if matches!(unit, SiPrefix::Base) { + write!(f, "{} {unit}", self.value) + } else { + let base_value = unit.base_value(); + let size = value / (base_value as f64); + write!(f, "{size:.2} {unit}") + } + } + PrefixKind::Bin => { + if !self.human_readable { + return write!(f, "{} {}", self.value, BinPrefix::Base); + } + + let unit = BinPrefix::from(self.value); + + if matches!(unit, BinPrefix::Base) { + write!(f, "{} {unit}", self.value) + } else { + let base_value = unit.base_value(); + let size = value / (base_value as f64); + write!(f, "{size:.2} {unit}") + } + } + } + } +} + +#[test] +fn test_metric() { + let metric = Metric { + value: 100, + kind: MetricKind::Logical, + human_readable: false, + prefix_kind: PrefixKind::Bin, + }; + assert_eq!(format!("{}", metric), "100 B"); + + let metric = Metric { + value: 1000, + kind: MetricKind::Logical, + human_readable: true, + prefix_kind: PrefixKind::Si, + }; + assert_eq!(format!("{}", metric), "1.00 KB"); + + let metric = Metric { + value: 1000, + kind: MetricKind::Logical, + human_readable: true, + prefix_kind: PrefixKind::Bin, + }; + assert_eq!(format!("{}", metric), "1000 B"); + + let metric = Metric { + value: 1024, + kind: MetricKind::Logical, + human_readable: true, + prefix_kind: PrefixKind::Bin, + }; + assert_eq!(format!("{}", metric), "1.00 KiB"); + + let metric = Metric { + value: 2_u64.pow(20), + kind: MetricKind::Logical, + human_readable: true, + prefix_kind: PrefixKind::Bin, + }; + assert_eq!(format!("{}", metric), "1.00 MiB"); + + let metric = Metric { + value: 123454, + kind: MetricKind::Logical, + human_readable: false, + prefix_kind: PrefixKind::Bin, + }; + assert_eq!(format!("{}", metric), "123454 B"); +} diff --git a/src/disk_usage/file_size/line_count.rs b/src/disk_usage/file_size/line_count.rs new file mode 100644 index 00000000..f845daeb --- /dev/null +++ b/src/disk_usage/file_size/line_count.rs @@ -0,0 +1,3 @@ +pub struct Metric { + value: u64, +} diff --git a/src/disk_usage/file_size/mod.rs b/src/disk_usage/file_size/mod.rs new file mode 100644 index 00000000..8b4aab3e --- /dev/null +++ b/src/disk_usage/file_size/mod.rs @@ -0,0 +1,51 @@ +use crate::context::Context; +use clap::ValueEnum; +use std::{convert::From, ops::AddAssign}; + +pub mod byte; +//pub mod block; +//pub mod word_count; +//pub mod line_count; + +pub enum FileSize { + //Block(block::Metric), + //WordCount(word_count::Metric), + //LineCount(line_count::Metric), + Byte(byte::Metric), +} + +/// Determines between logical or physical size for display +#[derive(Copy, Clone, Debug, ValueEnum, Default)] +pub enum DiskUsage { + /// How many bytes does a file contain + Logical, + + /// How much actual space on disk in bytes, taking into account sparse files and compression. + #[default] + Physical, +} + +impl FileSize { + #[inline] + pub const fn value(&self) -> u64 { + match self { + Self::Byte(metric) => metric.value, + } + } +} + +impl AddAssign<&Self> for FileSize { + fn add_assign(&mut self, rhs: &Self) { + match self { + Self::Byte(metric) => metric.value += rhs.value(), + } + } +} + +impl From<&Context> for FileSize { + fn from(ctx: &Context) -> Self { + match ctx.disk_usage { + DiskUsage::Logical | DiskUsage::Physical => Self::Byte(byte::Metric::from(ctx)), + } + } +} diff --git a/src/disk_usage/file_size/word_count.rs b/src/disk_usage/file_size/word_count.rs new file mode 100644 index 00000000..f845daeb --- /dev/null +++ b/src/disk_usage/file_size/word_count.rs @@ -0,0 +1,3 @@ +pub struct Metric { + value: u64, +} diff --git a/src/disk_usage/logical.rs b/src/disk_usage/logical.rs deleted file mode 100644 index 6818cd49..00000000 --- a/src/disk_usage/logical.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::{ - ByteDisplay, - DuMetric, - units::PrefixKind, -}; -use std::{ - fmt::{self, Display}, - fs::Metadata, -}; - -pub struct Size { - pub value: u64, - pub prefix_kind: PrefixKind, - pub human_readable: bool, -} - -impl DuMetric for Size {} - -impl Size { - pub fn new(metadata: &Metadata, prefix_kind: PrefixKind, human_readable: bool) -> Self { - let value = metadata.len(); - - Self { - value, - prefix_kind, - human_readable, - } - } -} - -impl ByteDisplay for Size { - fn human_readable(&self) -> bool { - self.human_readable - } - - fn prefix_kind(&self) -> PrefixKind { - self.prefix_kind - } - - fn value(&self) -> u64 { - self.value - } -} - -impl Display for Size { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(self, f) - } -} - -#[test] -fn test_logical_size() -> std::io::Result<()> { - use std::fs; - - let md = fs::metadata("./tests/data/nemesis.txt")?; - - let logical_size = Size::new(&md, PrefixKind::Bin, false); - - let display = format!("{logical_size}"); - - assert_eq!(logical_size.value, 161); - assert_eq!(display, "161 B"); - - assert_eq!( - format!( - "{}", - Size { - value: 1_024, - prefix_kind: PrefixKind::Bin, - human_readable: true - } - ), - "1.00 KiB" - ); - - Ok(()) -} diff --git a/src/disk_usage/mod.rs b/src/disk_usage/mod.rs index ba94ac2e..84582ca7 100644 --- a/src/disk_usage/mod.rs +++ b/src/disk_usage/mod.rs @@ -1,53 +1,4 @@ -use std::fmt; -use units::{BinPrefix, PrefixKind, SiPrefix, UnitPrefix}; - /// Binary and SI prefixes pub mod units; -/// Rules to display disk usage for individual files. pub mod file_size; - -pub mod block; - -pub mod logical; - -pub mod physical; - -pub trait DuMetric {} - -pub trait ByteDisplay { - fn prefix_kind(&self) -> PrefixKind; - - fn value(&self) -> u64; - - fn human_readable(&self) -> bool; - - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let value = self.value() as f64; - - match self.prefix_kind() { - PrefixKind::Si => { - let unit = SiPrefix::from(value); - let base_value = unit.base_value(); - - if !self.human_readable() || matches!(unit, SiPrefix::Base) { - write!(f, "{} {unit}", self.value()) - } else { - let size = value / (base_value as f64); - write!(f, "{size:.2} {unit}") - } - } - PrefixKind::Bin => { - let unit = BinPrefix::from(value); - let base_value = unit.base_value(); - - if !self.human_readable() || matches!(unit, BinPrefix::Base) { - write!(f, "{} {unit}", self.value()) - } else { - let size = value / (base_value as f64); - write!(f, "{size:.2} {unit}") - } - } - } - } -} diff --git a/src/disk_usage/physical.rs b/src/disk_usage/physical.rs deleted file mode 100644 index d31174bf..00000000 --- a/src/disk_usage/physical.rs +++ /dev/null @@ -1,68 +0,0 @@ -use filesize::PathExt; -use super::{ - units::PrefixKind, - ByteDisplay, - DuMetric, -}; -use std::{ - path::Path, - fmt::{self, Display}, - fs::Metadata, -}; - -pub struct Size { - pub value: u64, - pub prefix_kind: PrefixKind, - pub human_readable: bool, -} - -impl DuMetric for Size {} - -impl Size { - pub fn new(path: &Path, metadata: &Metadata, prefix_kind: PrefixKind, human_readable: bool) -> Self { - let value = path.size_on_disk_fast(metadata).unwrap_or(0); - - Self { - value, - prefix_kind, - human_readable, - } - } -} - -impl ByteDisplay for Size { - fn human_readable(&self) -> bool { - self.human_readable - } - - fn prefix_kind(&self) -> PrefixKind { - self.prefix_kind - } - - fn value(&self) -> u64 { - self.value - } -} - -impl Display for Size { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(self, f) - } -} - -#[test] -fn test_physical_size() -> std::io::Result<()> { - assert_eq!( - format!( - "{}", - Size { - value: 1_024, - prefix_kind: PrefixKind::Bin, - human_readable: true - } - ), - "1.00 KiB" - ); - - Ok(()) -} diff --git a/src/disk_usage/units.rs b/src/disk_usage/units.rs index bf605c29..92e1da3f 100644 --- a/src/disk_usage/units.rs +++ b/src/disk_usage/units.rs @@ -64,9 +64,9 @@ impl UnitPrefix for BinPrefix { } /// Get the closest human-readable unit prefix for value. -impl From for BinPrefix { - fn from(value: f64) -> Self { - let log = value.log2(); +impl From for BinPrefix { + fn from(value: u64) -> Self { + let log = (value as f64).log2(); if log < 10. { Self::Base @@ -83,9 +83,9 @@ impl From for BinPrefix { } /// Get the closest human-readable unit prefix for value. -impl From for SiPrefix { - fn from(value: f64) -> Self { - let log = value.log10(); +impl From for SiPrefix { + fn from(value: u64) -> Self { + let log = (value as f64).log10(); if log < 3. { Self::Base diff --git a/src/icons/fs.rs b/src/icons/fs.rs new file mode 100644 index 00000000..b19f08f8 --- /dev/null +++ b/src/icons/fs.rs @@ -0,0 +1,95 @@ +use ansi_term::{ANSIGenericString, Style}; +use ignore::DirEntry; +use std::{borrow::Cow, path::Path}; + +/// Computes a plain, colorless icon with given parameters. +/// +/// The precedent from highest to lowest in terms of which parameters determine the icon used +/// is as followed: file-type, file-extension, and then file-name. If an icon cannot be +/// computed the fall-back default icon is used. +/// +/// If a directory entry is a link and the link target is provided, the link target will be +/// used to determine the icon. +pub fn compute(entry: &DirEntry, link_target: Option<&Path>) -> Cow<'static, str> { + let icon = entry + .file_type() + .and_then(super::icon_from_file_type) + .map(Cow::from); + + if let Some(i) = icon { + return i; + } + + let ext = match link_target { + Some(target) if entry.path_is_symlink() => target.extension(), + _ => entry.path().extension(), + }; + + let icon = ext + .and_then(super::icon_from_ext) + .map(|(_, i)| Cow::from(i)); + + if let Some(i) = icon { + return i; + } + + let icon = super::icon_from_file_name(entry.file_name()).map(Cow::from); + + if let Some(i) = icon { + return i; + } + + Cow::from(super::get_default_icon().1) +} + +/// Computes a plain, colored icon with given parameters. See [compute] for more details. +pub fn compute_with_color( + entry: &DirEntry, + link_target: Option<&Path>, + style: Option