diff --git a/src/disk_usage/file_size/byte.rs b/src/disk_usage/file_size/byte.rs index bff25dd1..ac0f4fd4 100644 --- a/src/disk_usage/file_size/byte.rs +++ b/src/disk_usage/file_size/byte.rs @@ -1,8 +1,6 @@ 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, @@ -38,6 +36,24 @@ impl Metric { } } + pub const fn init_empty_logical(human_readable: bool, prefix_kind: PrefixKind) -> Self { + Self { + value: 0, + human_readable, + kind: MetricKind::Logical, + prefix_kind, + } + } + + pub const fn init_empty_physical(human_readable: bool, prefix_kind: PrefixKind) -> Self { + Self { + value: 0, + human_readable, + kind: MetricKind::Physical, + prefix_kind, + } + } + pub fn init_physical( path: &Path, metadata: &Metadata, @@ -56,22 +72,6 @@ impl Metric { } } -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; diff --git a/src/disk_usage/file_size/line_count.rs b/src/disk_usage/file_size/line_count.rs index f845daeb..ac0bbc52 100644 --- a/src/disk_usage/file_size/line_count.rs +++ b/src/disk_usage/file_size/line_count.rs @@ -1,3 +1,51 @@ +use std::{ + convert::{AsRef, From}, + fmt::{self, Display}, + fs, + path::Path, +}; + +#[derive(Default)] pub struct Metric { - value: u64, + pub value: u64, +} + +impl Metric { + pub fn init_lc>(path: P) -> Option { + let data = fs::read(path.as_ref()).ok(); + + let Some(text) = data else { + return None; + }; + + let lines = text.into_iter().fold(0, |acc, item| { + if u32::from(item) == u32::from('\n') { + acc + 1 + } else { + acc + } + }); + + u64::try_from(lines).map(|value| Self { value }).ok() + } +} + +impl From for Metric { + fn from(value: u64) -> Self { + Self { value } + } +} + +impl Display for Metric { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(&self.value, f) + } +} + +#[test] +fn test_lc() { + let metric = Metric::init_lc("tests/data/nemesis.txt") + .expect("Expected 'tests/data/nemesis.txt' to exist"); + + assert_eq!(metric.value, 4); } diff --git a/src/disk_usage/file_size/mod.rs b/src/disk_usage/file_size/mod.rs index 8b4aab3e..8e57d5ae 100644 --- a/src/disk_usage/file_size/mod.rs +++ b/src/disk_usage/file_size/mod.rs @@ -5,12 +5,12 @@ use std::{convert::From, ops::AddAssign}; pub mod byte; //pub mod block; //pub mod word_count; -//pub mod line_count; +pub mod line_count; pub enum FileSize { //Block(block::Metric), //WordCount(word_count::Metric), - //LineCount(line_count::Metric), + Line(line_count::Metric), Byte(byte::Metric), } @@ -23,6 +23,9 @@ pub enum DiskUsage { /// How much actual space on disk in bytes, taking into account sparse files and compression. #[default] Physical, + + /// How many total lines a file contains + Line, } impl FileSize { @@ -30,6 +33,7 @@ impl FileSize { pub const fn value(&self) -> u64 { match self { Self::Byte(metric) => metric.value, + Self::Line(metric) => metric.value, } } } @@ -38,6 +42,7 @@ impl AddAssign<&Self> for FileSize { fn add_assign(&mut self, rhs: &Self) { match self { Self::Byte(metric) => metric.value += rhs.value(), + Self::Line(metric) => metric.value += rhs.value(), } } } @@ -45,7 +50,11 @@ impl AddAssign<&Self> for FileSize { impl From<&Context> for FileSize { fn from(ctx: &Context) -> Self { match ctx.disk_usage { - DiskUsage::Logical | DiskUsage::Physical => Self::Byte(byte::Metric::from(ctx)), + DiskUsage::Logical => Self::Byte(byte::Metric::init_empty_logical(ctx.human, ctx.unit)), + DiskUsage::Physical => { + Self::Byte(byte::Metric::init_empty_physical(ctx.human, ctx.unit)) + } + DiskUsage::Line => Self::Line(line_count::Metric::default()), } } } diff --git a/src/render/grid/cell.rs b/src/render/grid/cell.rs index 36b3247e..f3eecf27 100644 --- a/src/render/grid/cell.rs +++ b/src/render/grid/cell.rs @@ -1,6 +1,9 @@ use crate::{ context::Context, - disk_usage::{file_size::FileSize, units::PrefixKind}, + disk_usage::{ + file_size::{byte, line_count, FileSize}, + units::PrefixKind, + }, render::theme, styles::{self, PLACEHOLDER}, tree::node::Node, @@ -107,24 +110,8 @@ impl<'a> Cell<'a> { }; match file_size { - FileSize::Byte(metric) => { - let max_size_width = ctx.max_size_width; - let max_unit_width = ctx.max_size_unit_width; - let out = format!("{metric}"); - let [size, unit]: [&str; 2] = - out.split(' ').collect::>().try_into().unwrap(); - - if ctx.no_color() { - write!(f, "{size:>max_size_width$} {unit:>max_unit_width$}") - } else { - let color = styles::get_du_theme().unwrap().get(unit).unwrap(); - - let out = - color.paint(format!("{size:>max_size_width$} {unit:>max_unit_width$}")); - - write!(f, "{out}") - } - } + FileSize::Byte(metric) => Self::fmt_bytes(f, metric, ctx), + FileSize::Line(metric) => Self::fmt_line_count(f, metric, ctx), } } @@ -244,7 +231,7 @@ impl<'a> Cell<'a> { } #[inline] - pub fn fmt_size_placeholder(f: &mut fmt::Formatter<'_>, ctx: &Context) -> fmt::Result { + fn fmt_size_placeholder(f: &mut fmt::Formatter<'_>, ctx: &Context) -> fmt::Result { if ctx.suppress_size || ctx.max_size_width == 0 { return write!(f, ""); } @@ -265,6 +252,40 @@ impl<'a> Cell<'a> { write!(f, "{placeholder:>placeholder_padding$}") } + + #[inline] + fn fmt_bytes(f: &mut fmt::Formatter<'_>, metric: &byte::Metric, ctx: &Context) -> fmt::Result { + let max_size_width = ctx.max_size_width; + let max_unit_width = ctx.max_size_unit_width; + let out = format!("{metric}"); + let [size, unit]: [&str; 2] = out.split(' ').collect::>().try_into().unwrap(); + + if ctx.no_color() { + return write!(f, "{size:>max_size_width$} {unit:>max_unit_width$}"); + } + + let color = styles::get_du_theme().unwrap().get(unit).unwrap(); + + let out = color.paint(format!("{size:>max_size_width$} {unit:>max_unit_width$}")); + + write!(f, "{out}") + } + + #[inline] + fn fmt_line_count( + f: &mut fmt::Formatter<'_>, + metric: &line_count::Metric, + ctx: &Context, + ) -> fmt::Result { + let max_size_width = ctx.max_size_width; + + if ctx.no_color() { + return write!(f, "{metric:>max_size_width$}"); + } + let color = styles::get_du_theme().unwrap().get("B").unwrap(); + + write!(f, "{}", color.paint(format!("{metric:>max_size_width$}"))) + } } impl Display for Cell<'_> { diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 340b9a73..726d552e 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -1,7 +1,7 @@ use crate::{ context::{file, output::ColumnProperties, Context}, disk_usage::{ - file_size::FileSize, + file_size::{DiskUsage, FileSize}, units::{BinPrefix, PrefixKind, SiPrefix}, }, fs::inode::Inode, @@ -315,20 +315,25 @@ impl Tree { col_props.max_size_width = file_size_cols; } - let unit_len = match ctx.unit { - PrefixKind::Bin if ctx.human => match BinPrefix::from(file_size.value()) { - BinPrefix::Base => 1, - _ => 3, - }, - PrefixKind::Si if ctx.human => match SiPrefix::from(file_size.value()) { - SiPrefix::Base => 1, - _ => 2, - }, - _ => 1, - }; - - if unit_len > col_props.max_size_unit_width { - col_props.max_size_unit_width = unit_len; + match ctx.disk_usage { + DiskUsage::Logical | DiskUsage::Physical => { + let unit_len = match ctx.unit { + PrefixKind::Bin if ctx.human => match BinPrefix::from(file_size.value()) { + BinPrefix::Base => 1, + _ => 3, + }, + PrefixKind::Si if ctx.human => match SiPrefix::from(file_size.value()) { + SiPrefix::Base => 1, + _ => 2, + }, + _ => 1, + }; + + if unit_len > col_props.max_size_unit_width { + col_props.max_size_unit_width = unit_len; + } + } + DiskUsage::Line => (), } } @@ -369,20 +374,25 @@ impl Tree { col_props.max_size_width = file_size_cols; } - let unit_len = match ctx.unit { - PrefixKind::Bin if ctx.human => match BinPrefix::from(file_size.value()) { - BinPrefix::Base => 1, - _ => 3, - }, - PrefixKind::Si if ctx.human => match SiPrefix::from(file_size.value()) { - SiPrefix::Base => 1, - _ => 2, - }, - _ => 1, - }; - - if unit_len > col_props.max_size_unit_width { - col_props.max_size_unit_width = unit_len; + match ctx.disk_usage { + DiskUsage::Logical | DiskUsage::Physical => { + let unit_len = match ctx.unit { + PrefixKind::Bin if ctx.human => match BinPrefix::from(file_size.value()) { + BinPrefix::Base => 1, + _ => 3, + }, + PrefixKind::Si if ctx.human => match SiPrefix::from(file_size.value()) { + SiPrefix::Base => 1, + _ => 2, + }, + _ => 1, + }; + + if unit_len > col_props.max_size_unit_width { + col_props.max_size_unit_width = unit_len; + } + } + _ => (), } } } diff --git a/src/tree/node/mod.rs b/src/tree/node/mod.rs index 444c6b06..33ce7f5f 100644 --- a/src/tree/node/mod.rs +++ b/src/tree/node/mod.rs @@ -1,6 +1,6 @@ use crate::{ context::Context, - disk_usage::file_size::{byte, DiskUsage, FileSize}, + disk_usage::file_size::{byte, line_count, DiskUsage, FileSize}, fs::inode::Inode, icons, styles::get_ls_colors, @@ -245,6 +245,10 @@ impl TryFrom<(DirEntry, &Context)> for Node { byte::Metric::init_physical(path, &metadata, ctx.unit, ctx.human); Some(FileSize::Byte(metric)) } + DiskUsage::Line => { + let metric = line_count::Metric::init_lc(path); + metric.map(FileSize::Line) + } } } else { None diff --git a/tests/line_count.rs b/tests/line_count.rs new file mode 100644 index 00000000..dcd19472 --- /dev/null +++ b/tests/line_count.rs @@ -0,0 +1,24 @@ +use indoc::indoc; + +mod utils; + +#[test] +fn line_count() { + assert_eq!( + utils::run_cmd(&["--disk-usage", "line", "tests/data"]), + indoc!( + "6 ┌─ cassildas_song.md + 6 ┌─ the_yellow_king + 1 ├─ nylarlathotep.txt + 4 ├─ nemesis.txt + 2 ├─ necronomicon.txt + 1 │ ┌─ lipsum.txt + 1 ├─ lipsum +10 │ ┌─ polaris.txt +10 ├─ dream_cycle +24 data + +3 directories, 6 files" + ) + ) +}