diff --git a/Cargo.lock b/Cargo.lock index d3009ac5..bc25b3c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,7 @@ dependencies = [ "clap", "clap_complete", "dirs", + "errno 0.3.1", "filesize", "ignore", "indextree", @@ -253,13 +254,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -631,7 +632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" dependencies = [ "bitflags", - "errno 0.3.0", + "errno 0.3.1", "io-lifetimes", "libc", "linux-raw-sys 0.3.1", @@ -944,6 +945,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-targets" version = "0.42.2" diff --git a/Cargo.toml b/Cargo.toml index 68e4f6a1..e179b3fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ chrono = "0.4.24" clap = { version = "4.1.1", features = ["derive"] } clap_complete = "4.1.1" dirs = "5.0" +errno = "0.3.1" filesize = "0.2.0" ignore = "0.4.2" indextree = "4.6.0" diff --git a/src/context/output.rs b/src/context/column.rs similarity index 76% rename from src/context/output.rs rename to src/context/column.rs index 79f6c680..16bf5f82 100644 --- a/src/context/output.rs +++ b/src/context/column.rs @@ -3,7 +3,7 @@ use std::convert::From; /// Utility struct to help store maximum column widths for attributes of each node. Each width is /// measured as the number of columns of the tty's window. -pub struct ColumnProperties { +pub struct Properties { pub max_size_width: usize, pub max_size_unit_width: usize, @@ -15,9 +15,15 @@ pub struct ColumnProperties { #[cfg(unix)] pub max_block_width: usize, + + #[cfg(unix)] + pub max_owner_width: usize, + + #[cfg(unix)] + pub max_group_width: usize, } -impl From<&Context> for ColumnProperties { +impl From<&Context> for Properties { fn from(ctx: &Context) -> Self { let unit_width = match ctx.unit { PrefixKind::Bin if ctx.human => 3, @@ -34,6 +40,10 @@ impl From<&Context> for ColumnProperties { max_ino_width: 0, #[cfg(unix)] max_block_width: 0, + #[cfg(unix)] + max_owner_width: 0, + #[cfg(unix)] + max_group_width: 0, } } } diff --git a/src/context/mod.rs b/src/context/mod.rs index d7e9269e..abb3fe8a 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -7,7 +7,6 @@ use ignore::{ overrides::{Override, OverrideBuilder}, DirEntry, }; -use output::ColumnProperties; use regex::Regex; use std::{ borrow::Borrow, @@ -35,7 +34,7 @@ pub mod file; pub mod layout; /// Utilities to print output. -pub mod output; +pub mod column; /// Printing order kinds. pub mod sort; @@ -87,6 +86,21 @@ pub struct Context { #[arg(short, long)] pub long: bool, + /// Show file's groups + #[cfg(unix)] + #[arg(long)] + pub group: bool, + + /// Show each file's ino + #[cfg(unix)] + #[arg(long)] + pub ino: bool, + + /// Show the total number of hardlinks to the underlying inode + #[cfg(unix)] + #[arg(long)] + pub nlink: bool, + /// Show permissions in numeric octal format instead of symbolic #[cfg(unix)] #[arg(long, requires = "long")] @@ -142,6 +156,10 @@ pub struct Context { #[arg(short, long, value_enum, default_value_t = PrefixKind::default())] pub unit: PrefixKind, + /// Which kind of layout to use when rendering the output + #[arg(short = 'y', long, value_enum, default_value_t = layout::Type::default())] + pub layout: layout::Type, + /// Show hidden files #[arg(short = '.', long)] pub hidden: bool, @@ -158,10 +176,6 @@ pub struct Context { #[arg(long)] pub dirs_only: bool, - /// Which kind of layout to use when rendering the output - #[arg(long, value_enum, default_value_t = layout::Type::default())] - pub layout: layout::Type, - /// Don't read configuration file #[arg(long)] pub no_config: bool, @@ -208,6 +222,16 @@ pub struct Context { #[cfg(unix)] pub max_block_width: usize, + /// Restricts column width of file owner for long view + #[clap(skip = usize::default())] + #[cfg(unix)] + pub max_owner_width: usize, + + /// Restricts column width of file group for long view + #[clap(skip = usize::default())] + #[cfg(unix)] + pub max_group_width: usize, + /// Width of the terminal emulator's window #[clap(skip)] pub window_width: Option, @@ -486,12 +510,14 @@ impl Context { } /// Update column width properties. - pub fn update_column_properties(&mut self, col_props: &ColumnProperties) { + pub fn update_column_properties(&mut self, col_props: &column::Properties) { self.max_size_width = col_props.max_size_width; self.max_size_unit_width = col_props.max_size_unit_width; #[cfg(unix)] { + self.max_owner_width = col_props.max_owner_width; + self.max_group_width = col_props.max_group_width; self.max_nlink_width = col_props.max_nlink_width; self.max_block_width = col_props.max_block_width; self.max_ino_width = col_props.max_ino_width; diff --git a/src/disk_usage/file_size/byte.rs b/src/disk_usage/file_size/byte.rs index 4ecfef78..b3baca34 100644 --- a/src/disk_usage/file_size/byte.rs +++ b/src/disk_usage/file_size/byte.rs @@ -1,6 +1,7 @@ use super::super::units::{BinPrefix, PrefixKind, SiPrefix, UnitPrefix}; use filesize::PathExt; use std::{ + cell::{Ref, RefCell}, fmt::{self, Display}, fs::Metadata, path::Path, @@ -14,6 +15,11 @@ pub struct Metric { #[allow(dead_code)] kind: MetricKind, prefix_kind: PrefixKind, + + /// To prevent allocating the same string twice. We allocate the first time + /// in [`crate::tree::update_column_properties`] in order to compute the max column width for + /// human-readable size and the second time during the actual render. + cached_display: RefCell, } /// Represents the appropriate method in which to compute bytes. `Logical` represent the total amount @@ -39,26 +45,29 @@ impl Metric { human_readable, kind, prefix_kind, + cached_display: RefCell::default(), } } /// Initializes an empty [Metric] used to represent the total amount of bytes of a file. - pub const fn init_empty_logical(human_readable: bool, prefix_kind: PrefixKind) -> Self { + pub fn init_empty_logical(human_readable: bool, prefix_kind: PrefixKind) -> Self { Self { value: 0, human_readable, kind: MetricKind::Logical, prefix_kind, + cached_display: RefCell::default(), } } /// Initializes an empty [Metric] used to represent the total disk space of a file in bytes. - pub const fn init_empty_physical(human_readable: bool, prefix_kind: PrefixKind) -> Self { + pub fn init_empty_physical(human_readable: bool, prefix_kind: PrefixKind) -> Self { Self { value: 0, human_readable, kind: MetricKind::Physical, prefix_kind, + cached_display: RefCell::default(), } } @@ -77,46 +86,66 @@ impl Metric { human_readable, kind, prefix_kind, + cached_display: RefCell::default(), } } + + /// Returns an immutable borrow of the `cached_display`. + pub fn cached_display(&self) -> Ref<'_, String> { + self.cached_display.borrow() + } } impl Display for Metric { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let value = self.value as f64; + { + let cached_display = self.cached_display(); - match self.prefix_kind { - PrefixKind::Si => { - if !self.human_readable { - return write!(f, "{} {}", self.value, SiPrefix::Base); - } + if cached_display.len() > 0 { + return write!(f, "{cached_display}"); + } + } - let unit = SiPrefix::from(self.value); + let value = self.value as f64; - if matches!(unit, SiPrefix::Base) { - write!(f, "{} {unit}", self.value) + let display = match self.prefix_kind { + PrefixKind::Si => { + if self.human_readable { + let unit = SiPrefix::from(self.value); + + if matches!(unit, SiPrefix::Base) { + format!("{} {unit}", self.value) + } else { + let base_value = unit.base_value(); + let size = value / (base_value as f64); + format!("{size:.1} {unit}") + } } else { - let base_value = unit.base_value(); - let size = value / (base_value as f64); - write!(f, "{size:.2} {unit}") + format!("{} {}", self.value, SiPrefix::Base) } } 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) + if self.human_readable { + let unit = BinPrefix::from(self.value); + + if matches!(unit, BinPrefix::Base) { + format!("{} {unit}", self.value) + } else { + let base_value = unit.base_value(); + let size = value / (base_value as f64); + format!("{size:.1} {unit}") + } } else { - let base_value = unit.base_value(); - let size = value / (base_value as f64); - write!(f, "{size:.2} {unit}") + format!("{} {}", self.value, BinPrefix::Base) } } - } + }; + + write!(f, "{display}")?; + + self.cached_display.replace(display); + + Ok(()) } } @@ -127,6 +156,7 @@ fn test_metric() { kind: MetricKind::Logical, human_readable: false, prefix_kind: PrefixKind::Bin, + cached_display: RefCell::::default(), }; assert_eq!(format!("{}", metric), "100 B"); @@ -135,14 +165,16 @@ fn test_metric() { kind: MetricKind::Logical, human_readable: true, prefix_kind: PrefixKind::Si, + cached_display: RefCell::::default(), }; - assert_eq!(format!("{}", metric), "1.00 KB"); + assert_eq!(format!("{}", metric), "1.0 KB"); let metric = Metric { value: 1000, kind: MetricKind::Logical, human_readable: true, prefix_kind: PrefixKind::Bin, + cached_display: RefCell::::default(), }; assert_eq!(format!("{}", metric), "1000 B"); @@ -151,22 +183,25 @@ fn test_metric() { kind: MetricKind::Logical, human_readable: true, prefix_kind: PrefixKind::Bin, + cached_display: RefCell::::default(), }; - assert_eq!(format!("{}", metric), "1.00 KiB"); + assert_eq!(format!("{}", metric), "1.0 KiB"); let metric = Metric { value: 2_u64.pow(20), kind: MetricKind::Logical, human_readable: true, prefix_kind: PrefixKind::Bin, + cached_display: RefCell::::default(), }; - assert_eq!(format!("{}", metric), "1.00 MiB"); + assert_eq!(format!("{}", metric), "1.0 MiB"); let metric = Metric { value: 123454, kind: MetricKind::Logical, human_readable: false, prefix_kind: PrefixKind::Bin, + cached_display: RefCell::::default(), }; assert_eq!(format!("{}", metric), "123454 B"); } diff --git a/src/disk_usage/file_size/mod.rs b/src/disk_usage/file_size/mod.rs index fd210412..ff06745e 100644 --- a/src/disk_usage/file_size/mod.rs +++ b/src/disk_usage/file_size/mod.rs @@ -1,6 +1,10 @@ use crate::context::Context; use clap::ValueEnum; -use std::{convert::From, ops::AddAssign}; +use std::{ + convert::From, + fmt::{self, Display}, + ops::AddAssign, +}; /// Concerned with measuring file size in blocks. #[cfg(unix)] @@ -15,11 +19,15 @@ pub mod line_count; /// Concerned with measuring file size by word count. pub mod word_count; +#[cfg(unix)] +pub const BLOCK_SIZE_BYTES: u16 = 512; + /// Represents all the different ways in which a filesize could be reported using various metrics. pub enum FileSize { Word(word_count::Metric), Line(line_count::Metric), Byte(byte::Metric), + #[cfg(unix)] Block(block::Metric), } @@ -87,3 +95,16 @@ impl From<&Context> for FileSize { } } } + +impl Display for FileSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Word(metric) => write!(f, "{metric}"), + Self::Line(metric) => write!(f, "{metric}"), + Self::Byte(metric) => write!(f, "{metric}"), + + #[cfg(unix)] + Self::Block(metric) => write!(f, "{metric}"), + } + } +} diff --git a/src/disk_usage/units.rs b/src/disk_usage/units.rs index 92e1da3f..d2100c5e 100644 --- a/src/disk_usage/units.rs +++ b/src/disk_usage/units.rs @@ -35,6 +35,32 @@ pub enum SiPrefix { Tera, } +impl SiPrefix { + /// Returns the human readable representation of the SI prefix. + pub const fn as_str(&self) -> &str { + match self { + Self::Base => "B", + Self::Kilo => "KB", + Self::Mega => "MB", + Self::Giga => "GB", + Self::Tera => "TB", + } + } +} + +impl BinPrefix { + /// Returns the human readable representation of the binary prefix. + pub const fn as_str(&self) -> &str { + match self { + Self::Base => "B", + Self::Kibi => "KiB", + Self::Mebi => "MiB", + Self::Gibi => "GiB", + Self::Tebi => "TiB", + } + } +} + pub trait UnitPrefix { fn base_value(&self) -> u64; } @@ -103,24 +129,12 @@ impl From for SiPrefix { impl Display for BinPrefix { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Base => f.pad("B"), - Self::Kibi => f.pad("KiB"), - Self::Mebi => f.pad("MiB"), - Self::Gibi => f.pad("GiB"), - Self::Tebi => f.pad("TiB"), - } + write!(f, "{}", self.as_str()) } } impl Display for SiPrefix { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Base => f.pad("B"), - Self::Kilo => f.pad("KB"), - Self::Mega => f.pad("MB"), - Self::Giga => f.pad("GB"), - Self::Tera => f.pad("TB"), - } + write!(f, "{}", self.as_str()) } } diff --git a/src/fs/mod.rs b/src/fs/mod.rs index a266260f..9c262071 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -8,10 +8,14 @@ pub mod inode; #[cfg(unix)] pub mod permissions; -#[cfg(unix)] /// Determining whether or not a file has extended attributes. +#[cfg(unix)] pub mod xattr; +/// Concerned with determining group and owner of file. +#[cfg(unix)] +pub mod ug; + /// Returns the path to the target of the soft link. Returns `None` if provided `dir_entry` isn't a /// symlink. pub fn symlink_target(dir_entry: &DirEntry) -> Option { diff --git a/src/fs/ug.rs b/src/fs/ug.rs new file mode 100644 index 00000000..675c618a --- /dev/null +++ b/src/fs/ug.rs @@ -0,0 +1,64 @@ +use errno::{errno, set_errno, Errno}; +use std::{ffi::CStr, fs::Metadata, os::unix::fs::MetadataExt}; + +type Owner = String; +type Group = String; + +impl UserGroupInfo for Metadata {} + +/// Trait that allows for files to query their owner and group. +pub trait UserGroupInfo: MetadataExt { + /// Attemps to query the owner of the implementor. + fn try_get_owner(&self) -> Result { + unsafe { + let uid = self.uid(); + try_get_user(uid) + } + } + + /// Attempts to query both the owner and group of the implementor. + fn try_get_owner_and_group(&self) -> Result<(Owner, Group), Errno> { + unsafe { + let uid = self.uid(); + let gid = self.gid(); + let user = try_get_user(uid)?; + let group = try_get_group(gid)?; + + Ok((user, group)) + } + } +} + +/// Attempts to return the name of the group associated with `gid`. +unsafe fn try_get_group(gid: libc::gid_t) -> Result { + set_errno(Errno(0)); + + let group = libc::getgrgid(gid); + + let errno = errno(); + + if errno.0 != 0 { + return Err(errno); + } + + let libc::group { gr_name, .. } = *group; + + Ok(CStr::from_ptr(gr_name).to_string_lossy().to_string()) +} + +/// Attempts to return the name of the user associated with `uid`. +unsafe fn try_get_user(uid: libc::uid_t) -> Result { + set_errno(Errno(0)); + + let pwd = libc::getpwuid(uid); + + let errno = errno(); + + if errno.0 != 0 { + return Err(errno); + } + + let libc::passwd { pw_name, .. } = *pwd; + + Ok(CStr::from_ptr(pw_name).to_string_lossy().to_string()) +} diff --git a/src/main.rs b/src/main.rs index ea69ca84..e33ae735 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ )] #![allow( clippy::struct_excessive_bools, + clippy::too_many_arguments, clippy::cast_precision_loss, clippy::cast_sign_loss, clippy::cast_possible_truncation diff --git a/src/render/grid/cell.rs b/src/render/grid/cell.rs index 028479fa..1d59a581 100644 --- a/src/render/grid/cell.rs +++ b/src/render/grid/cell.rs @@ -1,8 +1,8 @@ use crate::{ - context::{Context, time}, + context::Context, disk_usage::{ file_size::{byte, DiskUsage, FileSize}, - units::PrefixKind, + units::{BinPrefix, PrefixKind, SiPrefix}, }, render::theme, styles::{self, PLACEHOLDER}, @@ -16,6 +16,12 @@ use std::{ path::Path, }; +#[cfg(unix)] +use crate::{ + context::time, + disk_usage::file_size::{block, BLOCK_SIZE_BYTES}, +}; + /// Constitutes a single cell in a given row of the output. The `kind` field denotes what type of /// data actually goes into the cell once rendered. Each `kind` which is of type [Kind] has its own /// rules for rendering. Cell's do not have to be of a consistent width. @@ -39,9 +45,11 @@ pub enum Kind<'a> { #[cfg(unix)] Nlink, #[cfg(unix)] - Blocks, - #[cfg(unix)] Permissions, + #[cfg(unix)] + Owner, + #[cfg(unix)] + Group, } impl<'a> Cell<'a> { @@ -116,33 +124,10 @@ impl<'a> Cell<'a> { FileSize::Word(metric) => Self::fmt_unitless_disk_usage(f, metric, ctx), #[cfg(unix)] - FileSize::Block(metric) => Self::fmt_unitless_disk_usage(f, metric, ctx), + FileSize::Block(metric) => Self::fmt_block_usage(f, metric, ctx), } } - /// Rules on how to format block for rendering - #[cfg(unix)] - #[inline] - fn fmt_blocks(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let node = self.node; - let ctx = self.ctx; - - let max_width = ctx.max_block_width; - - let out = node - .blocks() - .map(|num| format!("{num:>max_width$}")) - .unwrap_or(format!("{PLACEHOLDER:>max_width$}")); - - let formatted_blocks = if let Ok(style) = styles::get_block_style() { - style.paint(out).to_string() - } else { - out - }; - - write!(f, "{formatted_blocks}") - } - /// Rules on how to format nlink for rendering. #[cfg(unix)] #[inline] @@ -189,6 +174,38 @@ impl<'a> Cell<'a> { write!(f, "{formatted_ino}") } + /// Rules on how to format owner. + #[cfg(unix)] + #[inline] + fn fmt_owner(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let max_owner_width = self.ctx.max_owner_width; + + let owner = self.node.owner().map_or(styles::PLACEHOLDER, |o| o); + + if let Ok(style) = styles::get_owner_style() { + let formatted_owner = format!("{owner:>max_owner_width$}"); + return write!(f, "{}", style.paint(formatted_owner)); + } + + write!(f, "{owner:>max_owner_width$}") + } + + /// Rules on how to format group. + #[cfg(unix)] + #[inline] + fn fmt_group(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let max_group_width = self.ctx.max_group_width; + + let group = self.node.group().map_or(styles::PLACEHOLDER, |o| o); + + if let Ok(style) = styles::get_group_style() { + let formatted_group = format!("{group:>max_group_width$}"); + return write!(f, "{}", style.paint(formatted_group)); + } + + write!(f, "{group:>max_group_width$}") + } + /// Rules on how to format datetime for rendering. #[cfg(unix)] #[inline] @@ -216,7 +233,7 @@ impl<'a> Cell<'a> { write!(f, "{formatted_datetime}") } - /// Rules on how to format timestamp + /// Rules on how to format timestamp #[cfg(unix)] #[inline] fn fmt_timestamp(&self, dt: DateTime) -> String { @@ -228,7 +245,7 @@ impl<'a> Cell<'a> { time::Format::Short => dt.format("%Y-%m-%d"), }; - format!("{:>12}", delayed_format) + format!("{delayed_format:>12}") } /// Rules on how to format permissions for rendering @@ -247,6 +264,7 @@ impl<'a> Cell<'a> { write!(f, "{formatted_perms}") } + /// Formatter for the placeholder for file sizes. #[inline] fn fmt_size_placeholder(f: &mut fmt::Formatter<'_>, ctx: &Context) -> fmt::Result { if ctx.suppress_size || ctx.max_size_width == 0 { @@ -273,24 +291,71 @@ impl<'a> Cell<'a> { write!(f, "{placeholder:>placeholder_padding$}") } + /// Rules to format disk usage as bytes #[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 color = if metric.human_readable { + styles::get_du_theme().unwrap().get(unit).unwrap() + } else { + match ctx.unit { + PrefixKind::Si => { + let pre = SiPrefix::from(metric.value); + styles::get_du_theme().unwrap().get(pre.as_str()).unwrap() + } + PrefixKind::Bin => { + let pre = BinPrefix::from(metric.value); + styles::get_du_theme().unwrap().get(pre.as_str()).unwrap() + } + } + }; let out = color.paint(format!("{size:>max_size_width$} {unit:>max_unit_width$}")); write!(f, "{out}") } + #[inline] + #[cfg(unix)] + fn fmt_block_usage( + f: &mut fmt::Formatter<'_>, + metric: &block::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 bytes = metric.value * u64::from(BLOCK_SIZE_BYTES); + + let color = match ctx.unit { + PrefixKind::Si => { + let pre = SiPrefix::from(bytes); + styles::get_du_theme().unwrap().get(pre.as_str()).unwrap() + } + PrefixKind::Bin => { + let pre = BinPrefix::from(bytes); + styles::get_du_theme().unwrap().get(pre.as_str()).unwrap() + } + }; + + let out = color.paint(format!("{metric:>max_size_width$}")); + + write!(f, "{out}") + } + + /// Rules to format disk usage as unit-less values such as word count, lines, and blocks (unix). #[inline] fn fmt_unitless_disk_usage( f: &mut fmt::Formatter<'_>, @@ -321,14 +386,17 @@ impl Display for Cell<'_> { #[cfg(unix)] Kind::Nlink => self.fmt_nlink(f), - #[cfg(unix)] - Kind::Blocks => self.fmt_blocks(f), - #[cfg(unix)] Kind::Datetime => self.fmt_datetime(f), #[cfg(unix)] Kind::Permissions => self.fmt_permissions(f), + + #[cfg(unix)] + Kind::Owner => self.fmt_owner(f), + + #[cfg(unix)] + Kind::Group => self.fmt_group(f), } } } diff --git a/src/render/grid/mod.rs b/src/render/grid/mod.rs index 2f46a883..d847ff54 100644 --- a/src/render/grid/mod.rs +++ b/src/render/grid/mod.rs @@ -5,6 +5,9 @@ use std::{ marker::PhantomData, }; +#[cfg(unix)] +use super::long; + /// Concerned with rules to construct and a single cell in a given row. pub mod cell; @@ -48,13 +51,10 @@ impl Display for Row<'_, Tree> { ); let row = if ctx.long { - let ino = Cell::new(node, ctx, cell::Kind::Ino); - let perms = Cell::new(node, ctx, cell::Kind::Permissions); - let nlink = Cell::new(node, ctx, cell::Kind::Nlink); - let blocks = Cell::new(node, ctx, cell::Kind::Blocks); - let time = Cell::new(node, ctx, cell::Kind::Datetime); + let optionals = long::Optionals::from(ctx); + let long_display = long::Display::new(optionals, node, ctx); - format!("{ino} {perms} {nlink} {blocks} {time} {size} {name}") + format!("{long_display} {size} {name}") } else { format!("{size} {name}") }; @@ -69,22 +69,23 @@ impl Display for Row<'_, Tree> { } } -#[cfg(not(unix))] -impl Display for Row<'_, Tree> { +#[cfg(unix)] +impl Display for Row<'_, Flat> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let node = self.node; let ctx = self.ctx; let size = Cell::new(node, ctx, cell::Kind::FileSize); - let name = Cell::new( - node, - ctx, - cell::Kind::FileName { - prefix: self.prefix, - }, - ); + let path = Cell::new(node, ctx, cell::Kind::FilePath); - let row = format!("{size} {name}"); + let row = if ctx.long { + let optionals = long::Optionals::from(ctx); + let long_display = long::Display::new(optionals, node, ctx); + + format!("{long_display} {size} {path}") + } else { + format!("{size} {path}") + }; if ctx.truncate && ctx.window_width.is_some() { let window_width = ctx.window_width.unwrap(); @@ -96,26 +97,22 @@ impl Display for Row<'_, Tree> { } } -#[cfg(unix)] -impl Display for Row<'_, Flat> { +#[cfg(not(unix))] +impl Display for Row<'_, Tree> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let node = self.node; let ctx = self.ctx; let size = Cell::new(node, ctx, cell::Kind::FileSize); - let path = Cell::new(node, ctx, cell::Kind::FilePath); - - let row = if ctx.long { - let ino = Cell::new(node, ctx, cell::Kind::Ino); - let perms = Cell::new(node, ctx, cell::Kind::Permissions); - let nlink = Cell::new(node, ctx, cell::Kind::Nlink); - let blocks = Cell::new(node, ctx, cell::Kind::Blocks); - let time = Cell::new(node, ctx, cell::Kind::Datetime); + let name = Cell::new( + node, + ctx, + cell::Kind::FileName { + prefix: self.prefix, + }, + ); - format!("{ino} {perms} {nlink} {blocks} {time} {size} {path}") - } else { - format!("{size} {path}") - }; + let row = format!("{size} {name}"); if ctx.truncate && ctx.window_width.is_some() { let window_width = ctx.window_width.unwrap(); diff --git a/src/render/long/mod.rs b/src/render/long/mod.rs new file mode 100644 index 00000000..cd38cff2 --- /dev/null +++ b/src/render/long/mod.rs @@ -0,0 +1,138 @@ +use super::grid::cell::{self, Cell}; +use crate::{context::Context, tree::node::Node}; +use std::{convert::From, fmt}; + +/// Concerned with displaying that actual attributes associated with the long view. +pub struct Display<'a> { + node: &'a Node, + ctx: &'a Context, + optional: Optionals, +} + +/// Optionals fields that are displayed when `--long` is specified. Each field is a boolean, +/// specifying whether or not a particular field should be included in the output when the long +/// view is enabled. +pub struct Optionals { + #[allow(dead_code)] + perms: bool, + #[allow(dead_code)] + owner: bool, + group: bool, + ino: bool, + #[allow(dead_code)] + nlink: bool, + #[allow(dead_code)] + time: bool, +} + +impl<'a> Display<'a> { + /// Constructor for [`Display`]. + pub const fn new(optional: Optionals, node: &'a Node, ctx: &'a Context) -> Self { + Self { + node, + ctx, + optional, + } + } +} + +/// Default implementation for [`Optionals`]. Fields that are `true` are the default fields that +/// should display when the long view is enabled. +impl Default for Optionals { + fn default() -> Self { + Self { + perms: true, + owner: true, + group: false, + ino: false, + nlink: false, + time: true, + } + } +} + +impl fmt::Display for Display<'_> { + /// Formatting the attributes associated with the long view. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Optionals { + group, ino, nlink, .. + } = self.optional; + let node = self.node; + let ctx = self.ctx; + + let perms = Cell::new(node, ctx, cell::Kind::Permissions); + let owner = Cell::new(node, ctx, cell::Kind::Owner); + let time = Cell::new(node, ctx, cell::Kind::Datetime); + + match (group, ino, nlink) { + (false, false, false) => { + write!(f, "{perms} {owner} {time}") + } + + (true, true, true) => { + let group_out = Cell::new(node, ctx, cell::Kind::Group); + let ino_out = Cell::new(node, ctx, cell::Kind::Ino); + let nlink_out = Cell::new(node, ctx, cell::Kind::Nlink); + + write!( + f, + "{ino_out} {perms} {nlink_out} {owner} {group_out} {time}" + ) + } + + (true, false, false) => { + let group_out = Cell::new(node, ctx, cell::Kind::Group); + + write!(f, "{perms} {owner} {group_out} {time}") + } + + (true, true, false) => { + let group_out = Cell::new(node, ctx, cell::Kind::Group); + let ino_out = Cell::new(node, ctx, cell::Kind::Ino); + + write!(f, "{ino_out} {perms} {owner} {group_out} {time}") + } + + (false, false, true) => { + let nlink_out = Cell::new(node, ctx, cell::Kind::Nlink); + + write!(f, "{perms} {nlink_out} {owner} {time}") + } + + (true, false, true) => { + let group_out = Cell::new(node, ctx, cell::Kind::Group); + let nlink_out = Cell::new(node, ctx, cell::Kind::Nlink); + + write!(f, "{perms} {nlink_out} {owner} {group_out} {time}") + } + + (false, true, false) => { + let ino_out = Cell::new(node, ctx, cell::Kind::Ino); + + write!(f, "{ino_out} {perms} {owner} {time}") + } + + (false, true, true) => { + let ino_out = Cell::new(node, ctx, cell::Kind::Ino); + let nlink_out = Cell::new(node, ctx, cell::Kind::Nlink); + + write!(f, "{ino_out} {perms} {nlink_out} {owner} {time}") + } + } + } +} + +impl From<&Context> for Optionals { + fn from(ctx: &Context) -> Self { + let Context { + group, ino, nlink, .. + } = *ctx; + + Self { + group, + ino, + nlink, + ..Self::default() + } + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 70cd2b5a..d70a5ad5 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -11,6 +11,10 @@ pub mod grid; /// output tree. pub mod theme; +/// Concerned with how to construct the long output. +#[cfg(unix)] +pub mod long; + /// The struct that is generic over T, which is generally expected to be a unit-struct that /// ultimately determines which variant to use for the output. pub struct Engine { diff --git a/src/styles/mod.rs b/src/styles/mod.rs index a87c6e0f..73a32d0e 100644 --- a/src/styles/mod.rs +++ b/src/styles/mod.rs @@ -61,6 +61,14 @@ static PLACEHOLDER_STYLE: OnceCell