From 9f3626908bcbb5bb868ed7b6513c6f37f15dfc64 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Thu, 27 Apr 2023 11:29:03 -0400 Subject: [PATCH 1/6] remove unnecessary nesting of modules --- src/{render => }/context/config.rs | 0 src/{render => }/context/error.rs | 0 src/{render => }/context/file.rs | 0 src/{render => }/context/mod.rs | 0 src/{render => }/context/output.rs | 0 src/{render => }/context/sort.rs | 0 src/{render => }/context/test.rs | 2 +- src/{render => }/context/time.rs | 0 src/{render => }/disk_usage/file_size.rs | 2 +- src/{render => }/disk_usage/mod.rs | 0 src/{render => }/disk_usage/units.rs | 0 src/main.rs | 28 ++++++++++++------- src/render/mod.rs | 14 ---------- src/{render => }/styles/error.rs | 0 src/{render => }/styles/mod.rs | 0 src/{render => }/tree/count.rs | 0 src/{render => }/tree/display/mod.rs | 2 +- src/{render => }/tree/display/theme.rs | 2 +- src/{render => }/tree/error.rs | 6 ++-- src/{render => }/tree/mod.rs | 7 ++--- src/{render => }/tree/node/cmp.rs | 2 +- src/{render => }/tree/node/display/mod.rs | 3 +- .../tree/node/display/presenters.rs | 4 +-- src/{render => }/tree/node/mod.rs | 10 +++---- src/{render => }/tree/node/style.rs | 2 +- src/{render => }/tree/visitor.rs | 5 +++- 26 files changed, 42 insertions(+), 47 deletions(-) rename src/{render => }/context/config.rs (100%) rename src/{render => }/context/error.rs (100%) rename src/{render => }/context/file.rs (100%) rename src/{render => }/context/mod.rs (100%) rename src/{render => }/context/output.rs (100%) rename src/{render => }/context/sort.rs (100%) rename src/{render => }/context/test.rs (96%) rename src/{render => }/context/time.rs (100%) rename src/{render => }/disk_usage/file_size.rs (99%) rename src/{render => }/disk_usage/mod.rs (100%) rename src/{render => }/disk_usage/units.rs (100%) delete mode 100644 src/render/mod.rs rename src/{render => }/styles/error.rs (100%) rename src/{render => }/styles/mod.rs (100%) rename src/{render => }/tree/count.rs (100%) rename src/{render => }/tree/display/mod.rs (99%) rename src/{render => }/tree/display/theme.rs (98%) rename src/{render => }/tree/error.rs (89%) rename src/{render => }/tree/mod.rs (98%) rename src/{render => }/tree/node/cmp.rs (97%) rename src/{render => }/tree/node/display/mod.rs (98%) rename src/{render => }/tree/node/display/presenters.rs (96%) rename src/{render => }/tree/node/mod.rs (97%) rename src/{render => }/tree/node/style.rs (96%) rename src/{render => }/tree/visitor.rs (96%) diff --git a/src/render/context/config.rs b/src/context/config.rs similarity index 100% rename from src/render/context/config.rs rename to src/context/config.rs diff --git a/src/render/context/error.rs b/src/context/error.rs similarity index 100% rename from src/render/context/error.rs rename to src/context/error.rs diff --git a/src/render/context/file.rs b/src/context/file.rs similarity index 100% rename from src/render/context/file.rs rename to src/context/file.rs diff --git a/src/render/context/mod.rs b/src/context/mod.rs similarity index 100% rename from src/render/context/mod.rs rename to src/context/mod.rs diff --git a/src/render/context/output.rs b/src/context/output.rs similarity index 100% rename from src/render/context/output.rs rename to src/context/output.rs diff --git a/src/render/context/sort.rs b/src/context/sort.rs similarity index 100% rename from src/render/context/sort.rs rename to src/context/sort.rs diff --git a/src/render/context/test.rs b/src/context/test.rs similarity index 96% rename from src/render/context/test.rs rename to src/context/test.rs index 93fe74bd..7aa792ca 100644 --- a/src/render/context/test.rs +++ b/src/context/test.rs @@ -1,4 +1,4 @@ -use crate::render::context::sort; +use crate::context::sort; use clap::{CommandFactory, FromArgMatches}; use super::{config, Context}; diff --git a/src/render/context/time.rs b/src/context/time.rs similarity index 100% rename from src/render/context/time.rs rename to src/context/time.rs diff --git a/src/render/disk_usage/file_size.rs b/src/disk_usage/file_size.rs similarity index 99% rename from src/render/disk_usage/file_size.rs rename to src/disk_usage/file_size.rs index 9da5b937..7305e8ed 100644 --- a/src/render/disk_usage/file_size.rs +++ b/src/disk_usage/file_size.rs @@ -1,6 +1,6 @@ use super::units::{BinPrefix, PrefixKind, SiPrefix, UnitPrefix}; use crate::{ - render::styles::{self, get_du_theme, get_placeholder_style}, + styles::{self, get_du_theme, get_placeholder_style}, utils, Context, }; use ansi_term::Style; diff --git a/src/render/disk_usage/mod.rs b/src/disk_usage/mod.rs similarity index 100% rename from src/render/disk_usage/mod.rs rename to src/disk_usage/mod.rs diff --git a/src/render/disk_usage/units.rs b/src/disk_usage/units.rs similarity index 100% rename from src/render/disk_usage/units.rs rename to src/disk_usage/units.rs diff --git a/src/main.rs b/src/main.rs index aace35df..9d9b2dab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,26 +16,34 @@ clippy::cast_possible_truncation )] use clap::CommandFactory; -use render::{ - context::Context, - tree::{ - display::{Flat, Inverted, Regular}, - Tree, - }, +use context::Context; +use tree::{ + display::{Flat, Inverted, Regular}, + Tree, }; use std::{io::stdout, process::ExitCode}; /// Operations to wrangle ANSI escaped strings. mod ansi; +/// CLI rules and definitions as well as context to be injected throughout the entire program. +mod context; + +/// Operations relevant to the computation and presentation of disk usage. +mod disk_usage; + /// Filesystem operations. mod fs; -/// Dev icons. +/// All things related to icons on how to map certain files to the appropriate icons. mod icons; -/// Tools and operations to display root-directory. -mod render; +/// Global used throughout the program to paint the output. +mod styles; + +/// Houses the primary data structures that are used to virtualize the filesystem, containing also +/// information on how the tree output should be ultimately rendered. +mod tree; /// Utilities relating to interacting with tty properties. mod tty; @@ -60,7 +68,7 @@ fn run() -> Result<(), Box> { return Ok(()); } - render::styles::init(ctx.no_color()); + styles::init(ctx.no_color()); if ctx.flat { let tree = Tree::::try_init(ctx)?; diff --git a/src/render/mod.rs b/src/render/mod.rs deleted file mode 100644 index 88dbe780..00000000 --- a/src/render/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -/// CLI rules and definitions and context wherein [`Tree`] will operate. -/// -/// [`Tree`]: tree::Tree -pub mod context; - -/// Operations that decide how to present info about disk usage. -pub mod disk_usage; - -/// Encapsulates everything related to the in-memory representation of the root directory and its -/// contents. -pub mod tree; - -/// Global styles. -pub mod styles; diff --git a/src/render/styles/error.rs b/src/styles/error.rs similarity index 100% rename from src/render/styles/error.rs rename to src/styles/error.rs diff --git a/src/render/styles/mod.rs b/src/styles/mod.rs similarity index 100% rename from src/render/styles/mod.rs rename to src/styles/mod.rs diff --git a/src/render/tree/count.rs b/src/tree/count.rs similarity index 100% rename from src/render/tree/count.rs rename to src/tree/count.rs diff --git a/src/render/tree/display/mod.rs b/src/tree/display/mod.rs similarity index 99% rename from src/render/tree/display/mod.rs rename to src/tree/display/mod.rs index d8d9fbcb..2d266d74 100644 --- a/src/render/tree/display/mod.rs +++ b/src/tree/display/mod.rs @@ -1,4 +1,4 @@ -use crate::render::{ +use crate::{ styles, tree::{count::FileCount, node::Node, Tree}, }; diff --git a/src/render/tree/display/theme.rs b/src/tree/display/theme.rs similarity index 98% rename from src/render/tree/display/theme.rs rename to src/tree/display/theme.rs index 3df91c43..49d93d6b 100644 --- a/src/render/tree/display/theme.rs +++ b/src/tree/display/theme.rs @@ -1,4 +1,4 @@ -use crate::render::{ +use crate::{ styles::{self, ThemesMap}, tree::Node, }; diff --git a/src/render/tree/error.rs b/src/tree/error.rs similarity index 89% rename from src/render/tree/error.rs rename to src/tree/error.rs index ba01fdc5..b30e531d 100644 --- a/src/render/tree/error.rs +++ b/src/tree/error.rs @@ -1,5 +1,7 @@ -use super::styles::error::Error as StyleError; -use crate::render::context::error::Error as CtxError; +use crate::{ + context::error::Error as CtxError, + styles::error::Error as StyleError, +}; use ignore::Error as IgnoreError; use std::io::Error as IoError; diff --git a/src/render/tree/mod.rs b/src/tree/mod.rs similarity index 98% rename from src/render/tree/mod.rs rename to src/tree/mod.rs index 9104c4b9..fd9d7643 100644 --- a/src/render/tree/mod.rs +++ b/src/tree/mod.rs @@ -1,10 +1,7 @@ use crate::{ fs::inode::Inode, - render::{ - context::{file, output::ColumnProperties, Context}, - disk_usage::file_size::FileSize, - styles, - }, + context::{file, output::ColumnProperties, Context}, + disk_usage::file_size::FileSize, utils, }; use count::FileCount; diff --git a/src/render/tree/node/cmp.rs b/src/tree/node/cmp.rs similarity index 97% rename from src/render/tree/node/cmp.rs rename to src/tree/node/cmp.rs index 4a69ca8f..bf3f701b 100644 --- a/src/render/tree/node/cmp.rs +++ b/src/tree/node/cmp.rs @@ -1,5 +1,5 @@ use super::Node; -use crate::render::context::{sort, Context}; +use crate::context::{sort, Context}; use std::cmp::Ordering; /// Comparator type used to sort [Node]s. diff --git a/src/render/tree/node/display/mod.rs b/src/tree/node/display/mod.rs similarity index 98% rename from src/render/tree/node/display/mod.rs rename to src/tree/node/display/mod.rs index 55f73176..36f497b4 100644 --- a/src/render/tree/node/display/mod.rs +++ b/src/tree/node/display/mod.rs @@ -1,6 +1,7 @@ use crate::{ ansi::Escaped, - render::{context::Context, tree::node::Node}, + context::Context, + tree::node::Node, }; use std::{ borrow::Cow, diff --git a/src/render/tree/node/display/presenters.rs b/src/tree/node/display/presenters.rs similarity index 96% rename from src/render/tree/node/display/presenters.rs rename to src/tree/node/display/presenters.rs index 251afccb..b069f2b1 100644 --- a/src/render/tree/node/display/presenters.rs +++ b/src/tree/node/display/presenters.rs @@ -1,8 +1,8 @@ -use crate::render::{context::Context, disk_usage::file_size::FileSize, tree::Node}; +use crate::{context::Context, disk_usage::file_size::FileSize, tree::Node}; use std::borrow::Cow; #[cfg(unix)] -use crate::render::{ +use crate::{ context::time::Stamp, styles::{self, error::Error, PLACEHOLDER}, }; diff --git a/src/render/tree/node/mod.rs b/src/tree/node/mod.rs similarity index 97% rename from src/render/tree/node/mod.rs rename to src/tree/node/mod.rs index 521d21d7..077828ae 100644 --- a/src/render/tree/node/mod.rs +++ b/src/tree/node/mod.rs @@ -1,12 +1,10 @@ use crate::{ fs::inode::Inode, icons, - render::{ - context::Context, - disk_usage::file_size::{DiskUsage, FileSize}, - styles::get_ls_colors, - tree::error::Error, - }, + context::Context, + disk_usage::file_size::{DiskUsage, FileSize}, + styles::get_ls_colors, + tree::error::Error, }; use ansi_term::Style; use ignore::DirEntry; diff --git a/src/render/tree/node/style.rs b/src/tree/node/style.rs similarity index 96% rename from src/render/tree/node/style.rs rename to src/tree/node/style.rs index 9bfeb693..87bb9393 100644 --- a/src/render/tree/node/style.rs +++ b/src/tree/node/style.rs @@ -5,7 +5,7 @@ use std::{borrow::Cow, ffi::OsStr}; #[cfg(unix)] use crate::{ fs::permissions::FileMode, - render::styles::{get_octal_permissions_style, get_permissions_theme}, + styles::{get_octal_permissions_style, get_permissions_theme}, }; impl Node { diff --git a/src/render/tree/visitor.rs b/src/tree/visitor.rs similarity index 96% rename from src/render/tree/visitor.rs rename to src/tree/visitor.rs index 8e8d85f8..1d81d53e 100644 --- a/src/render/tree/visitor.rs +++ b/src/tree/visitor.rs @@ -1,6 +1,9 @@ use std::sync::mpsc::Sender; -use super::{Context, Node}; +use crate::{ + Context, + tree::node::Node, +}; use ignore::{DirEntry, Error as IgnoreError, ParallelVisitor, ParallelVisitorBuilder, WalkState}; pub enum TraversalState { From af06c3d4b06700e2dac93018f5c592c49ab6e7d1 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sat, 29 Apr 2023 16:14:08 -0700 Subject: [PATCH 2/6] wip refactor --- src/context/layout.rs | 15 +++++++++++++++ src/context/mod.rs | 16 ++++++++-------- src/main.rs | 30 ++++++++++++++++++++---------- src/render/mod.rs | 21 +++++++++++++++++++++ src/tree/node/mod.rs | 2 +- 5 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 src/context/layout.rs create mode 100644 src/render/mod.rs diff --git a/src/context/layout.rs b/src/context/layout.rs new file mode 100644 index 00000000..4c7557b0 --- /dev/null +++ b/src/context/layout.rs @@ -0,0 +1,15 @@ +use clap::ValueEnum; + +/// Which layout to use when rendering the tree. +#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)] +pub enum Type { + /// Outputs the tree with the root node at the bottom of the output + #[default] + Regular, + + /// Outputs the tree with the root node at the top of the output + Inverted, + + /// Outputs a flat layout using paths rather than an ASCII tree. + Flat, +} diff --git a/src/context/mod.rs b/src/context/mod.rs index 0dbc1e9e..69be95f5 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -24,6 +24,9 @@ pub mod error; /// Common cross-platform file-types. pub mod file; +/// For determining the output layout. +pub mod layout; + /// Utilities to print output. pub mod output; @@ -60,10 +63,6 @@ pub struct Context { #[arg(short = 'f', long)] pub follow: bool, - /// Print disk usage information in plain format without the ASCII tree - #[arg(short = 'F', long)] - pub flat: bool, - /// Print disk usage in human-readable format #[arg(short = 'H', long)] pub human: bool, @@ -147,9 +146,9 @@ pub struct Context { #[arg(long)] pub dirs_only: bool, - /// Print tree with the root directory at the topmost position - #[arg(long)] - pub inverted: 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, /// Print plainly without ANSI escapes #[arg(long)] @@ -207,6 +206,7 @@ pub struct Context { } type Predicate = Result bool + Send + Sync + 'static>, Error>; + impl Context { /// Initializes [Context], optionally reading in the configuration file to override defaults. /// Arguments provided will take precedence over config. @@ -336,7 +336,7 @@ impl Context { /// files, directories will always be included since matched files will need to be bridged back /// to the root node somehow. Empty sets not producing an output is handled by [`Tree`]. /// - /// [`Tree`]: crate::render::tree::Tree + /// [`Tree`]: crate::tree::Tree pub fn regex_predicate(&self) -> Predicate { let Some(pattern) = self.pattern.as_ref() else { return Err(Error::PatternNotProvided); diff --git a/src/main.rs b/src/main.rs index 9d9b2dab..02200d0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ clippy::cast_possible_truncation )] use clap::CommandFactory; -use context::Context; +use context::{Context, layout}; use tree::{ display::{Flat, Inverted, Regular}, Tree, @@ -38,6 +38,12 @@ mod fs; /// All things related to icons on how to map certain files to the appropriate icons. mod icons; +/// Concerned with taking an initialized [`Tree`] and its [`Node`]s and rendering the output. +/// +/// [`Tree`]: tree::Tree +/// [`Node`]: tree::node::Node +mod render; + /// Global used throughout the program to paint the output. mod styles; @@ -70,15 +76,19 @@ fn run() -> Result<(), Box> { styles::init(ctx.no_color()); - if ctx.flat { - let tree = Tree::::try_init(ctx)?; - println!("{tree}"); - } else if ctx.inverted { - let tree = Tree::::try_init(ctx)?; - println!("{tree}"); - } else { - let tree = Tree::::try_init(ctx)?; - println!("{tree}"); + match ctx.layout { + layout::Type::Flat => { + let tree = Tree::::try_init(ctx)?; + println!("{tree}"); + }, + layout::Type::Inverted => { + let tree = Tree::::try_init(ctx)?; + println!("{tree}"); + }, + layout::Type::Regular => { + let tree = Tree::::try_init(ctx)?; + println!("{tree}"); + }, } Ok(()) diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 00000000..4b4a14d5 --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,21 @@ +use crate::{ + context::Context, + tree::Tree, +}; +use std::marker::PhantomData; + +pub struct Regular; +pub struct Inverted; +pub struct Flat; + +pub struct Engine { + ctx: Context, + //tree: Tree, + layout: PhantomData +} + +impl Engine { + pub fn new(ctx: Context) -> Self { + Self { ctx, layout: PhantomData } + } +} diff --git a/src/tree/node/mod.rs b/src/tree/node/mod.rs index 077828ae..d926c228 100644 --- a/src/tree/node/mod.rs +++ b/src/tree/node/mod.rs @@ -202,7 +202,7 @@ impl Node { /// Formats the [Node] for the [`Flat`] presentation. /// - /// [`Flat`]: crate::render::tree::display::Flat + /// [`Flat`]: crate::tree::display::Flat pub fn flat_display(&self, f: &mut Formatter, ctx: &Context) -> fmt::Result { self.flat(f, ctx) } From 19ce627590314b564ea1ff53aec8cd56c22d352d Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Tue, 2 May 2023 15:02:12 -0700 Subject: [PATCH 3/6] render engine --- src/main.rs | 20 +-- src/render/display/flat.rs | 4 +- src/render/display/inverted.rs | 4 +- src/render/display/regular.rs | 4 +- src/render/mod.rs | 38 +++++- src/tree/display/mod.rs | 230 --------------------------------- src/tree/display/theme.rs | 37 ------ src/tree/mod.rs | 47 ++----- src/tree/node/cmp.rs | 4 +- tests/flat.rs | 6 +- 10 files changed, 63 insertions(+), 331 deletions(-) delete mode 100644 src/tree/display/mod.rs delete mode 100644 src/tree/display/theme.rs diff --git a/src/main.rs b/src/main.rs index 11ebbb82..b0a1fef6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,10 +19,8 @@ use clap::CommandFactory; use context::{Context, layout}; -use tree::{ - display::{Flat, Inverted, Regular}, - Tree, -}; +use render::{Engine, Flat, Inverted, Regular}; +use tree::Tree; use std::{error::Error, io::stdout}; /// Operations to wrangle ANSI escaped strings. @@ -69,18 +67,20 @@ fn main() -> Result<(), Box> { styles::init(ctx.no_color()); + let (tree, ctx) = Tree::try_init_and_update_context(ctx)?; + match ctx.layout { layout::Type::Flat => { - let tree = Tree::::try_init(ctx)?; - println!("{tree}"); + let render = Engine::::new(tree, ctx); + println!("{render}"); }, layout::Type::Inverted => { - let tree = Tree::::try_init(ctx)?; - println!("{tree}"); + let render = Engine::::new(tree, ctx); + println!("{render}"); }, layout::Type::Regular => { - let tree = Tree::::try_init(ctx)?; - println!("{tree}"); + let render = Engine::::new(tree, ctx); + println!("{render}"); }, } diff --git a/src/render/display/flat.rs b/src/render/display/flat.rs index 925440dd..5c28783b 100644 --- a/src/render/display/flat.rs +++ b/src/render/display/flat.rs @@ -1,11 +1,9 @@ use crate::{ - render::Engine, + render::{Engine, Flat}, tree::{count::FileCount, Tree}, }; use std::fmt::{self, Display}; -pub struct Flat; - impl Display for Engine { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let ctx = self.context(); diff --git a/src/render/display/inverted.rs b/src/render/display/inverted.rs index bb08dee6..e93b6bfc 100644 --- a/src/render/display/inverted.rs +++ b/src/render/display/inverted.rs @@ -1,13 +1,11 @@ use crate::{ - render::{Engine, theme}, + render::{Engine, Inverted, theme}, styles, tree::{count::FileCount, node::Node, Tree}, }; use indextree::NodeId; use std::fmt::{self, Display}; -pub struct Inverted; - impl Display for Engine { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let ctx = self.context(); diff --git a/src/render/display/regular.rs b/src/render/display/regular.rs index 7e0af7a3..dc2f1fb1 100644 --- a/src/render/display/regular.rs +++ b/src/render/display/regular.rs @@ -1,13 +1,11 @@ use crate::{ - render::{Engine, theme}, + render::{Engine, Regular, theme}, styles, tree::{count::FileCount, node::Node, Tree}, }; use indextree::{NodeEdge, NodeId}; use std::fmt::{self, Display}; -pub struct Regular; - impl Display for Engine { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let ctx = self.context(); diff --git a/src/render/mod.rs b/src/render/mod.rs index 4b4a14d5..f9b47a28 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,18 +4,44 @@ use crate::{ }; use std::marker::PhantomData; -pub struct Regular; -pub struct Inverted; -pub struct Flat; +/// Module containing all of the output variants. +pub mod display; + +/// Utility module to fetch the appropriate theme used to paint the box-drawing characters of the +/// output tree. +pub mod theme; +/// 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 { ctx: Context, - //tree: Tree, + tree: Tree, layout: PhantomData } +/// The flat output that is similar to `du`, without the ASCII tree. +pub struct Flat; + +/// The tree output with the root directory at the bottom of the output. +pub struct Regular; + +/// The tree output with the root directory at the top of the output. More like the traditional +/// `tree` command. +pub struct Inverted; + impl Engine { - pub fn new(ctx: Context) -> Self { - Self { ctx, layout: PhantomData } + /// Initializes a new [Engine]. + pub fn new(tree: Tree, ctx: Context) -> Self { + Self { ctx, tree, layout: PhantomData } + } + + /// Getter for the inner [Context] object. + const fn context(&self) -> &Context { + &self.ctx + } + + /// Getter for the inner [Tree] data structure. + const fn tree(&self) -> &Tree { + &self.tree } } diff --git a/src/tree/display/mod.rs b/src/tree/display/mod.rs deleted file mode 100644 index 2d266d74..00000000 --- a/src/tree/display/mod.rs +++ /dev/null @@ -1,230 +0,0 @@ -use crate::{ - styles, - tree::{count::FileCount, node::Node, Tree}, -}; -use indextree::{NodeEdge, NodeId}; -use std::fmt::{self, Display, Formatter}; - -/// Empty trait used to constrain generic parameter `display_variant` of [Tree]. -pub trait TreeVariant {} - -/// For printing output with colored ANSI escapes. -pub struct Regular {} - -/// Prints the invered tree with colored ANSI escapes. -pub struct Inverted {} - -/// For generating plain-text report of disk usage without ASCII tree. -pub struct Flat {} - -impl TreeVariant for Regular {} -impl TreeVariant for Flat {} -impl TreeVariant for Inverted {} - -/// Utilities to pick the appropriate theme to paint box drawing characters. -mod theme; - -impl Display for Tree { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let ctx = self.context(); - let root_id = self.root_id; - let arena = self.arena(); - let max_depth = ctx.level(); - let mut file_count_data = vec![]; - - let mut display_node = |node_id: NodeId, node: &Node, prefix: &str| -> fmt::Result { - node.tree_display(f, prefix, ctx)?; - file_count_data.push(Self::compute_file_count(node_id, arena)); - writeln!(f) - }; - - let mut get_theme = if ctx.follow { - theme::link_theme_getter() - } else { - theme::regular_theme_getter() - }; - - let mut base_prefix_components = vec![""]; - - let mut tree_edges = root_id.reverse_traverse(arena).skip(1).peekable(); - - while let Some(node_edge) = tree_edges.next() { - let current_node_id = match node_edge { - NodeEdge::Start(id) => id, - - NodeEdge::End(id) => { - let current_node = arena[id].get(); - - if !current_node.is_dir() || id.children(arena).count() == 0 { - continue; - } - - let theme = get_theme(current_node); - - let topmost_sibling = id.following_siblings(arena).nth(1).is_none(); - - if topmost_sibling { - base_prefix_components.push(styles::SEP); - } else { - base_prefix_components.push(theme.get("vt").unwrap()); - } - - continue; - } - }; - - let current_node = arena[current_node_id].get(); - - let node_depth = current_node.depth(); - - let topmost_sibling = current_node_id.following_siblings(arena).nth(1).is_none(); - - let theme = get_theme(current_node); - - if node_depth <= max_depth { - if node_depth == 0 { - display_node(current_node_id, current_node, "")?; - } else { - let prefix_part = if topmost_sibling { - theme.get("drt").unwrap() - } else { - theme.get("vtrt").unwrap() - }; - - let mut current_prefix_components = base_prefix_components.clone(); - - current_prefix_components.push(prefix_part); - - let prefix = current_prefix_components.join(""); - - display_node(current_node_id, current_node, &prefix)?; - } - } - - if let Some(NodeEdge::Start(next_id)) = tree_edges.peek() { - let next_node = arena[*next_id].get(); - - if next_node.depth() < node_depth { - base_prefix_components.pop(); - } - } - } - - if !file_count_data.is_empty() { - write!(f, "\n{}", FileCount::from(file_count_data))?; - } - - Ok(()) - } -} - -impl Display for Tree { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let ctx = self.context(); - - let root_id = self.root_id; - let arena = self.arena(); - let level = ctx.level(); - let mut file_count_data = vec![]; - - let mut descendants = root_id.descendants(arena).skip(1).peekable(); - - let mut display_node = |node_id: NodeId, node: &Node, prefix: &str| -> fmt::Result { - node.tree_display(f, prefix, ctx)?; - file_count_data.push(Self::compute_file_count(node_id, arena)); - writeln!(f) - }; - - display_node(root_id, arena[root_id].get(), "")?; - - let mut get_theme = if ctx.follow { - theme::link_theme_getter() - } else { - theme::regular_theme_getter() - }; - - let mut base_prefix_components = vec![""]; - - while let Some(current_node_id) = descendants.next() { - let current_node = arena[current_node_id].get(); - - let current_depth = current_node.depth(); - - let mut siblings = current_node_id.following_siblings(arena).skip(1).peekable(); - - let last_sibling = siblings.peek().is_none(); - - let theme = get_theme(current_node); - - if current_depth <= level { - let prefix_part = if last_sibling { - theme.get("uprt").unwrap() - } else { - theme.get("vtrt").unwrap() - }; - - let mut current_prefix_components = base_prefix_components.clone(); - - current_prefix_components.push(prefix_part); - - let prefix = current_prefix_components.join(""); - - display_node(current_node_id, current_node, &prefix)?; - } - - if let Some(next_id) = descendants.peek() { - let next_node = arena[*next_id].get(); - - let next_depth = next_node.depth(); - - if next_depth == current_depth + 1 { - if last_sibling { - base_prefix_components.push(styles::SEP); - } else { - let prefix = theme.get("vt").unwrap(); - base_prefix_components.push(prefix); - } - } else if next_depth < current_depth { - let depth_delta = current_depth - next_depth; - - base_prefix_components.truncate(base_prefix_components.len() - depth_delta); - } - } - } - - if !file_count_data.is_empty() { - write!(f, "\n{}", FileCount::from(file_count_data))?; - } - - Ok(()) - } -} - -impl Display for Tree { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let tree = self.arena(); - let root_id = self.root_id(); - let ctx = self.context(); - let max_depth = ctx.level(); - let mut file_count_data = vec![]; - - let descendants = root_id.descendants(tree); - - for node_id in descendants { - let node = tree[node_id].get(); - - if node.depth() > max_depth { - continue; - } - - node.flat_display(f, ctx)?; - file_count_data.push(Self::compute_file_count(node_id, tree)); - } - - if !file_count_data.is_empty() { - write!(f, "\n{}", FileCount::from(file_count_data))?; - } - - Ok(()) - } -} diff --git a/src/tree/display/theme.rs b/src/tree/display/theme.rs deleted file mode 100644 index f3741803..00000000 --- a/src/tree/display/theme.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::{ - styles::{self, ThemesMap}, - tree::Node, -}; - -type Theme = Box &'static ThemesMap>; - -/// Returns a closure that retrieves the regular theme. -pub fn regular_theme_getter() -> Theme { - Box::new(|_node: &Node| styles::get_tree_theme().unwrap()) -} - -/// Returns a closure that can smartly determine when a symlink is being followed and when it is -/// not being followed. When a symlink is being followed, all of its descendents should have tree -/// branches that are colored differently. -pub fn link_theme_getter() -> Theme { - let mut link_depth = None; - - Box::new(move |node: &Node| { - let current_depth = node.depth(); - - if let Some(ldepth) = link_depth { - if current_depth == ldepth { - link_depth = None; - } - } - - if link_depth.is_some() || node.is_symlink() { - if node.is_dir() && link_depth.is_none() { - link_depth = Some(current_depth); - } - styles::get_link_theme().unwrap() - } else { - styles::get_tree_theme().unwrap() - } - }) -} diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 235e7509..08a2f21d 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -13,7 +13,6 @@ use std::{ collections::{HashMap, HashSet}, convert::TryFrom, fs, - marker::PhantomData, path::PathBuf, result::Result as StdResult, sync::mpsc::{self, Sender}, @@ -22,10 +21,7 @@ use std::{ use visitor::{BranchVisitorBuilder, TraversalState}; /// Operations to handle and display aggregate file counts based on their type. -mod count; - -/// Display variants for [Tree]. -pub mod display; +pub mod count; /// Errors related to traversal, [Tree] construction, and the like. pub mod error; @@ -40,34 +36,22 @@ pub mod node; mod visitor; /// Virtual data structure that represents local file-system hierarchy. -pub struct Tree -where - T: display::TreeVariant, -{ +pub struct Tree { arena: Arena, root_id: NodeId, - ctx: Context, - display_variant: PhantomData, } pub type Result = StdResult; -impl Tree -where - T: display::TreeVariant, -{ +impl Tree { /// Constructor for [Tree]. - pub const fn new(arena: Arena, root_id: NodeId, ctx: Context) -> Self { - Self { - arena, - root_id, - ctx, - display_variant: PhantomData, - } + pub const fn new(arena: Arena, root_id: NodeId) -> Self { + Self { arena, root_id } } - /// Initiates file-system traversal and [Tree construction]. - pub fn try_init(mut ctx: Context) -> Result { + /// Initiates file-system traversal and [Tree] as well as updates the [Context] object with + /// various properties necessary to render output. + pub fn try_init_and_update_context(mut ctx: Context) -> Result<(Self, Context)> { let mut column_properties = ColumnProperties::from(&ctx); let (arena, root_id) = Self::traverse(&ctx, &mut column_properties)?; @@ -78,13 +62,13 @@ where ctx.set_window_width(); } - let tree = Self::new(arena, root_id, ctx); + let tree = Self::new(arena, root_id); if tree.is_stump() { return Err(Error::NoMatches); } - Ok(tree) + Ok((tree, ctx)) } /// Returns `true` if there are no entries to show excluding the `root_id`. @@ -97,18 +81,13 @@ where .is_none() } - /// Grab a reference to [Context]. - pub const fn context(&self) -> &Context { - &self.ctx - } - /// Grab a reference to `root_id`. - const fn root_id(&self) -> NodeId { + pub const fn root_id(&self) -> NodeId { self.root_id } /// Grabs a reference to `arena`. - const fn arena(&self) -> &Arena { + pub const fn arena(&self) -> &Arena { &self.arena } @@ -315,7 +294,7 @@ where /// Compute total number of files for a single directory without recurring into child /// directories. Files are grouped into three categories: directories, regular files, and /// symlinks. - fn compute_file_count(node_id: NodeId, tree: &Arena) -> FileCount { + pub fn compute_file_count(node_id: NodeId, tree: &Arena) -> FileCount { let mut count = FileCount::default(); for child_id in node_id.children(tree) { diff --git a/src/tree/node/cmp.rs b/src/tree/node/cmp.rs index 8d46250f..3940dc97 100644 --- a/src/tree/node/cmp.rs +++ b/src/tree/node/cmp.rs @@ -127,7 +127,7 @@ fn base_comparator(sort_type: sort::Type) -> Box { //} mod sizing { - use crate::render::tree::node::Node; + use crate::tree::node::Node; use core::cmp::Ordering; /// Comparator that sorts [Node]s by size, largest to smallest. @@ -143,7 +143,7 @@ mod sizing { } mod naming { - use crate::render::tree::node::Node; + use crate::tree::node::Node; use core::cmp::Ordering; /// Comparator based on [Node] file names in lexicographical order. diff --git a/tests/flat.rs b/tests/flat.rs index dd75ac5f..f490437c 100644 --- a/tests/flat.rs +++ b/tests/flat.rs @@ -5,7 +5,7 @@ mod utils; #[test] fn flat() { assert_eq!( - utils::run_cmd(&["--flat", "tests/data"]), + utils::run_cmd(&["--layout", "flat", "tests/data"]), indoc!( "1241 B data 308 B dream_cycle @@ -26,7 +26,7 @@ fn flat() { #[test] fn flat_human() { assert_eq!( - utils::run_cmd(&["--flat", "--human", "tests/data"]), + utils::run_cmd(&["--layout", "flat", "--human", "tests/data"]), indoc!( "1.21 KiB data 308 B dream_cycle @@ -47,7 +47,7 @@ fn flat_human() { #[test] fn flat_with_level() { assert_eq!( - utils::run_cmd(&["--flat", "--level", "1", "tests/data"]), + utils::run_cmd(&["--layout", "flat", "--level", "1", "tests/data"]), indoc!( "1241 B data 308 B dream_cycle From da5e4e829667a048f90cf0a293ac289782ca9b72 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Tue, 2 May 2023 15:03:34 -0700 Subject: [PATCH 4/6] doc comments --- src/render/display/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/render/display/mod.rs b/src/render/display/mod.rs index e54455d5..cdd262bf 100644 --- a/src/render/display/mod.rs +++ b/src/render/display/mod.rs @@ -1,5 +1,8 @@ +/// See [super::Regular] pub mod regular; +/// See [super::Flat] pub mod flat; +/// See [super::Inverted] pub mod inverted; From 41adfa3ece095c3e129dd0d7f6fe616e6aaaf7c1 Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sun, 14 May 2023 13:12:24 -0700 Subject: [PATCH 5/6] decouple render logic from primary data structures --- src/context/sort.rs | 1 - src/main.rs | 10 +- src/render/display/mod.rs | 8 - src/render/grid/cell.rs | 261 +++++++++++++++++++++ src/render/grid/mod.rs | 149 ++++++++++++ src/render/{display => layout}/flat.rs | 19 +- src/render/{display => layout}/inverted.rs | 9 +- src/render/layout/mod.rs | 8 + src/render/{display => layout}/regular.rs | 9 +- src/render/mod.rs | 22 +- src/tree/error.rs | 5 +- src/tree/mod.rs | 2 +- src/tree/node/cmp.rs | 106 ++++----- src/tree/node/display/mod.rs | 153 ------------ src/tree/node/display/presenters.rs | 122 ---------- src/tree/node/mod.rs | 25 +- src/tree/node/style.rs | 8 +- src/tree/visitor.rs | 5 +- tests/flat.rs | 72 +++--- 19 files changed, 562 insertions(+), 432 deletions(-) delete mode 100644 src/render/display/mod.rs create mode 100644 src/render/grid/cell.rs create mode 100644 src/render/grid/mod.rs rename src/render/{display => layout}/flat.rs (65%) rename src/render/{display => layout}/inverted.rs (93%) create mode 100644 src/render/layout/mod.rs rename src/render/{display => layout}/regular.rs (94%) delete mode 100644 src/tree/node/display/mod.rs delete mode 100644 src/tree/node/display/presenters.rs diff --git a/src/context/sort.rs b/src/context/sort.rs index 05af88db..96e75049 100644 --- a/src/context/sort.rs +++ b/src/context/sort.rs @@ -14,7 +14,6 @@ pub enum Type { /// Sort entries by size largest to smallest, bottom to top SizeRev, - // Sort entries by newer to older Accessing Date //Access, diff --git a/src/main.rs b/src/main.rs index b0a1fef6..ea69ca84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,10 +18,10 @@ )] use clap::CommandFactory; -use context::{Context, layout}; +use context::{layout, Context}; use render::{Engine, Flat, Inverted, Regular}; -use tree::Tree; use std::{error::Error, io::stdout}; +use tree::Tree; /// Operations to wrangle ANSI escaped strings. mod ansi; @@ -73,15 +73,15 @@ fn main() -> Result<(), Box> { layout::Type::Flat => { let render = Engine::::new(tree, ctx); println!("{render}"); - }, + } layout::Type::Inverted => { let render = Engine::::new(tree, ctx); println!("{render}"); - }, + } layout::Type::Regular => { let render = Engine::::new(tree, ctx); println!("{render}"); - }, + } } Ok(()) diff --git a/src/render/display/mod.rs b/src/render/display/mod.rs deleted file mode 100644 index cdd262bf..00000000 --- a/src/render/display/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// See [super::Regular] -pub mod regular; - -/// See [super::Flat] -pub mod flat; - -/// See [super::Inverted] -pub mod inverted; diff --git a/src/render/grid/cell.rs b/src/render/grid/cell.rs new file mode 100644 index 00000000..8ab38d15 --- /dev/null +++ b/src/render/grid/cell.rs @@ -0,0 +1,261 @@ +use crate::{ + context::Context, + disk_usage::file_size::FileSize, + styles::{self, PLACEHOLDER}, + tree::node::Node, +}; +use std::{ + ffi::OsStr, + fmt::{self, Display}, + path::Path, +}; + +/// 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. +pub struct Cell<'a> { + ctx: &'a Context, + node: &'a Node, + kind: Kind<'a>, +} + +/// The type of data that a [Cell] should render. +pub enum Kind<'a> { + FileName { + prefix: Option<&'a str>, + }, + FilePath, + FileSize, + #[cfg(unix)] + Datetime, + #[cfg(unix)] + Ino, + #[cfg(unix)] + Nlink, + #[cfg(unix)] + Blocks, + #[cfg(unix)] + Permissions, +} + +impl<'a> Cell<'a> { + /// Initializes a new [Cell]. + pub const fn new(node: &'a Node, ctx: &'a Context, kind: Kind<'a>) -> Self { + Self { ctx, node, kind } + } + + /// Rules on how to render a file-name with icons and a prefix if applicable. The order in + /// which items are rendered are: prefix-icon-name. + #[inline] + fn fmt_name(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let node = self.node; + let ctx = self.ctx; + + match self.kind { + Kind::FileName { prefix } => { + let pre = prefix.unwrap_or(""); + + let name = node.symlink_target_file_name().map_or_else( + || Node::stylize(node.file_name(), node.style()), + |target_name| { + let link_name = node.file_name(); + Node::stylize_link_name(link_name, target_name, node.style()) + }, + ); + + if !ctx.icons { + return write!(f, "{pre}{name}"); + } + + let icon = node.compute_icon(ctx.no_color()); + + write!(f, "{pre}{icon} {name}") + } + + _ => unreachable!(), + } + } + + /// Rules on how to render a file's path + #[inline] + fn fmt_path(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let node = self.node; + let ctx = self.ctx; + + let path = if node.depth() == 0 { + let file_name = node.file_name(); + >::as_ref(file_name).display() + } else { + node.path() + .strip_prefix(ctx.dir_canonical()) + .unwrap_or_else(|_| node.path()) + .display() + }; + + if !ctx.icons { + return write!(f, "{path}"); + } + + let icon = node.compute_icon(ctx.no_color()); + + write!(f, "{icon} {path}") + } + + /// Rules on how to render the file size. + #[inline] + fn fmt_file_size(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let node = self.node; + let ctx = self.ctx; + + let formatted_size = node.file_size().map_or_else( + || FileSize::placeholder(ctx), + |size| size.format(ctx.max_size_width, ctx.max_size_unit_width), + ); + + write!(f, "{formatted_size}") + } + + /// 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] + fn fmt_nlink(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let node = self.node; + let ctx = self.ctx; + + let max_width = ctx.max_nlink_width; + + let out = node + .nlink() + .map(|num| format!("{num:>max_width$}")) + .unwrap_or(format!("{PLACEHOLDER:>max_width$}")); + + let formatted_nlink = if let Ok(style) = styles::get_nlink_style() { + style.paint(out).to_string() + } else { + out + }; + + write!(f, "{formatted_nlink}") + } + + /// Rules on how to format ino for rendering. + #[cfg(unix)] + #[inline] + fn fmt_ino(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let node = self.node; + let ctx = self.ctx; + + let max_width = ctx.max_ino_width; + + let out = node + .ino() + .map(|num| format!("{num:>max_width$}")) + .unwrap_or(format!("{PLACEHOLDER:>max_width$}")); + + let formatted_ino = if let Ok(style) = styles::get_ino_style() { + style.paint(out).to_string() + } else { + out + }; + + write!(f, "{formatted_ino}") + } + + /// Rules on how to format datetime for rendering. + #[cfg(unix)] + #[inline] + fn fmt_datetime(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use crate::context::time; + use chrono::{offset::Local, DateTime}; + + let node = self.node; + let ctx = self.ctx; + + let datetime = match ctx.time() { + time::Stamp::Created => node.created(), + time::Stamp::Accessed => node.accessed(), + time::Stamp::Modified => node.modified(), + }; + + let out = datetime.map(DateTime::::from).map_or_else( + || format!("{PLACEHOLDER:>12}"), + |dt| format!("{:>12}", dt.format("%d %h %H:%M %g")), + ); + + let formatted_datetime = if let Ok(style) = styles::get_datetime_style() { + style.paint(out).to_string() + } else { + out + }; + + write!(f, "{formatted_datetime}") + } + + /// Rules on how to format permissions for rendering + #[cfg(unix)] + #[inline] + fn fmt_permissions(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let node = self.node; + let ctx = self.ctx; + + let file_mode = node.mode().unwrap(); + + let formatted_perms = if ctx.octal { + Node::style_octal_permissions(&file_mode) + } else if node.has_xattrs() { + Node::style_sym_permissions(&file_mode, true) + } else { + Node::style_sym_permissions(&file_mode, false) + }; + + write!(f, "{formatted_perms}") + } +} + +impl Display for Cell<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + Kind::FileName { prefix: _prefix } => self.fmt_name(f), + Kind::FilePath => self.fmt_path(f), + Kind::FileSize => self.fmt_file_size(f), + + #[cfg(unix)] + Kind::Ino => self.fmt_ino(f), + + #[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), + } + } +} diff --git a/src/render/grid/mod.rs b/src/render/grid/mod.rs new file mode 100644 index 00000000..2f46a883 --- /dev/null +++ b/src/render/grid/mod.rs @@ -0,0 +1,149 @@ +use crate::{ansi::Escaped, tree::node::Node, Context}; +use cell::Cell; +use std::{ + fmt::{self, Display}, + marker::PhantomData, +}; + +/// Concerned with rules to construct and a single cell in a given row. +pub mod cell; + +pub struct Row<'a, T> { + prefix: Option<&'a str>, + ctx: &'a Context, + node: &'a Node, + layout: PhantomData, +} + +/// For both the [`super::Regular`] and [`super::Inverted`] layout variants. +pub struct Tree; + +/// For the [`super::Flat`] variant. +pub struct Flat; + +impl<'a, T> Row<'a, T> { + pub const fn new(node: &'a Node, ctx: &'a Context, prefix: Option<&'a str>) -> Row<'a, T> { + Self { + prefix, + node, + ctx, + layout: PhantomData, + } + } +} + +#[cfg(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 name = Cell::new( + node, + ctx, + cell::Kind::FileName { + prefix: self.prefix, + }, + ); + + 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); + + format!("{ino} {perms} {nlink} {blocks} {time} {size} {name}") + } else { + format!("{size} {name}") + }; + + if ctx.truncate && ctx.window_width.is_some() { + let window_width = ctx.window_width.unwrap(); + let out = ::truncate(&row, window_width); + write!(f, "{out}") + } else { + write!(f, "{row}") + } + } +} + +#[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 name = Cell::new( + node, + ctx, + cell::Kind::FileName { + prefix: self.prefix, + }, + ); + + let row = format!("{size} {name}"); + + if ctx.truncate && ctx.window_width.is_some() { + let window_width = ctx.window_width.unwrap(); + let out = ::truncate(&row, window_width); + write!(f, "{out}") + } else { + write!(f, "{row}") + } + } +} + +#[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 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); + + format!("{ino} {perms} {nlink} {blocks} {time} {size} {path}") + } else { + format!("{size} {path}") + }; + + if ctx.truncate && ctx.window_width.is_some() { + let window_width = ctx.window_width.unwrap(); + let out = ::truncate(&row, window_width); + write!(f, "{out}") + } else { + write!(f, "{row}") + } + } +} + +#[cfg(not(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 path = Cell::new(node, ctx, cell::Kind::FilePath); + + let row = format!("{size} {path}"); + + if ctx.truncate && ctx.window_width.is_some() { + let window_width = ctx.window_width.unwrap(); + let out = ::truncate(&row, window_width); + write!(f, "{out}") + } else { + write!(f, "{row}") + } + } +} diff --git a/src/render/display/flat.rs b/src/render/layout/flat.rs similarity index 65% rename from src/render/display/flat.rs rename to src/render/layout/flat.rs index 5c28783b..05eea3af 100644 --- a/src/render/display/flat.rs +++ b/src/render/layout/flat.rs @@ -1,7 +1,11 @@ use crate::{ - render::{Engine, Flat}, + render::{ + grid::{self, Row}, + Engine, Flat, + }, tree::{count::FileCount, Tree}, }; +use indextree::NodeEdge; use std::fmt::{self, Display}; impl Display for Engine { @@ -13,16 +17,21 @@ impl Display for Engine { let max_depth = ctx.level(); let mut file_count_data = vec![]; - let descendants = root_id.descendants(arena); - - for node_id in descendants { + for edge in root_id.reverse_traverse(arena) { + let node_id = match edge { + NodeEdge::Start(id) => id, + NodeEdge::End(_) => continue, + }; let node = arena[node_id].get(); if node.depth() > max_depth { continue; } - node.flat_display(f, ctx)?; + let row = Row::::new(node, ctx, None); + + writeln!(f, "{row}")?; + file_count_data.push(Tree::compute_file_count(node_id, arena)); } diff --git a/src/render/display/inverted.rs b/src/render/layout/inverted.rs similarity index 93% rename from src/render/display/inverted.rs rename to src/render/layout/inverted.rs index e93b6bfc..80d84225 100644 --- a/src/render/display/inverted.rs +++ b/src/render/layout/inverted.rs @@ -1,5 +1,8 @@ use crate::{ - render::{Engine, Inverted, theme}, + render::{ + grid::{self, Row}, + theme, Engine, Inverted, + }, styles, tree::{count::FileCount, node::Node, Tree}, }; @@ -19,9 +22,9 @@ impl Display for Engine { let mut descendants = root_id.descendants(arena).skip(1).peekable(); let mut display_node = |node_id: NodeId, node: &Node, prefix: &str| -> fmt::Result { - node.tree_display(f, prefix, ctx)?; + let row = Row::::new(node, ctx, Some(prefix)); file_count_data.push(Tree::compute_file_count(node_id, arena)); - writeln!(f) + writeln!(f, "{row}") }; display_node(root_id, arena[root_id].get(), "")?; diff --git a/src/render/layout/mod.rs b/src/render/layout/mod.rs new file mode 100644 index 00000000..29b727f8 --- /dev/null +++ b/src/render/layout/mod.rs @@ -0,0 +1,8 @@ +/// See [`super::Regular`] +pub mod regular; + +/// See [`super::Flat`] +pub mod flat; + +/// See [`super::Inverted`] +pub mod inverted; diff --git a/src/render/display/regular.rs b/src/render/layout/regular.rs similarity index 94% rename from src/render/display/regular.rs rename to src/render/layout/regular.rs index dc2f1fb1..d893c7c6 100644 --- a/src/render/display/regular.rs +++ b/src/render/layout/regular.rs @@ -1,5 +1,8 @@ use crate::{ - render::{Engine, Regular, theme}, + render::{ + grid::{self, Row}, + theme, Engine, Regular, + }, styles, tree::{count::FileCount, node::Node, Tree}, }; @@ -16,9 +19,9 @@ impl Display for Engine { let mut file_count_data = vec![]; let mut display_node = |node_id: NodeId, node: &Node, prefix: &str| -> fmt::Result { - node.tree_display(f, prefix, ctx)?; + let row = Row::::new(node, ctx, Some(prefix)); file_count_data.push(Tree::compute_file_count(node_id, arena)); - writeln!(f) + writeln!(f, "{row}") }; let mut get_theme = if ctx.follow { diff --git a/src/render/mod.rs b/src/render/mod.rs index f9b47a28..70cd2b5a 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,11 +1,11 @@ -use crate::{ - context::Context, - tree::Tree, -}; +use crate::{context::Context, tree::Tree}; use std::marker::PhantomData; -/// Module containing all of the output variants. -pub mod display; +/// Module containing all of the layout variants. +pub mod layout; + +/// Concerned with how to construct a single row in the output grid. +pub mod grid; /// Utility module to fetch the appropriate theme used to paint the box-drawing characters of the /// output tree. @@ -16,7 +16,7 @@ pub mod theme; pub struct Engine { ctx: Context, tree: Tree, - layout: PhantomData + layout: PhantomData, } /// The flat output that is similar to `du`, without the ASCII tree. @@ -31,8 +31,12 @@ pub struct Inverted; impl Engine { /// Initializes a new [Engine]. - pub fn new(tree: Tree, ctx: Context) -> Self { - Self { ctx, tree, layout: PhantomData } + pub const fn new(tree: Tree, ctx: Context) -> Self { + Self { + ctx, + tree, + layout: PhantomData, + } } /// Getter for the inner [Context] object. diff --git a/src/tree/error.rs b/src/tree/error.rs index b30e531d..f95e40d5 100644 --- a/src/tree/error.rs +++ b/src/tree/error.rs @@ -1,7 +1,4 @@ -use crate::{ - context::error::Error as CtxError, - styles::error::Error as StyleError, -}; +use crate::{context::error::Error as CtxError, styles::error::Error as StyleError}; use ignore::Error as IgnoreError; use std::io::Error as IoError; diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 08a2f21d..e5d4939c 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -1,7 +1,7 @@ use crate::{ - fs::inode::Inode, context::{file, output::ColumnProperties, Context}, disk_usage::file_size::FileSize, + fs::inode::Inode, utils, }; use count::FileCount; diff --git a/src/tree/node/cmp.rs b/src/tree/node/cmp.rs index 3940dc97..d9e371dd 100644 --- a/src/tree/node/cmp.rs +++ b/src/tree/node/cmp.rs @@ -71,59 +71,59 @@ fn base_comparator(sort_type: sort::Type) -> Box { } //mod time_stamping { - //pub mod accessed { - //use crate::render::tree::node::Node; - //use core::cmp::Ordering; - //use std::time::SystemTime; - - ///// Comparator that sorts [Node]s by Last Access timestamp, newer to older. - //pub fn comparator(a: &Node, b: &Node) -> Ordering { - //let a_stamp = a.accessed().unwrap_or_else(SystemTime::now); - //let b_stamp = b.accessed().unwrap_or_else(SystemTime::now); - //a_stamp.cmp(&b_stamp) - //} - - ///// Comparator that sorts [Node]s by Access timestamp, older to newer. - //pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { - //comparator(b, a) - //} - //} - - //pub mod created { - //use crate::render::tree::node::Node; - //use core::cmp::Ordering; - //use std::time::SystemTime; - - ///// Comparator that sorts [Node]s by Creation timestamp, newer to older. - //pub fn comparator(a: &Node, b: &Node) -> Ordering { - //let a_stamp = a.created().unwrap_or_else(SystemTime::now); - //let b_stamp = b.created().unwrap_or_else(SystemTime::now); - //a_stamp.cmp(&b_stamp) - //} - - ///// Comparator that sorts [Node]s by Creation timestamp, older to newer. - //pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { - //comparator(b, a) - //} - //} - - //pub mod modified { - //use crate::render::tree::node::Node; - //use core::cmp::Ordering; - //use std::time::SystemTime; - - ///// Comparator that sorts [Node]s by Alteration timestamp, newer to older. - //pub fn comparator(a: &Node, b: &Node) -> Ordering { - //let a_stamp = a.modified().unwrap_or_else(SystemTime::now); - //let b_stamp = b.modified().unwrap_or_else(SystemTime::now); - //a_stamp.cmp(&b_stamp) - //} - - ///// Comparator that sorts [Node]s by Alteration timestamp, older to newer. - //pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { - //comparator(b, a) - //} - //} +//pub mod accessed { +//use crate::render::tree::node::Node; +//use core::cmp::Ordering; +//use std::time::SystemTime; + +///// Comparator that sorts [Node]s by Last Access timestamp, newer to older. +//pub fn comparator(a: &Node, b: &Node) -> Ordering { +//let a_stamp = a.accessed().unwrap_or_else(SystemTime::now); +//let b_stamp = b.accessed().unwrap_or_else(SystemTime::now); +//a_stamp.cmp(&b_stamp) +//} + +///// Comparator that sorts [Node]s by Access timestamp, older to newer. +//pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { +//comparator(b, a) +//} +//} + +//pub mod created { +//use crate::render::tree::node::Node; +//use core::cmp::Ordering; +//use std::time::SystemTime; + +///// Comparator that sorts [Node]s by Creation timestamp, newer to older. +//pub fn comparator(a: &Node, b: &Node) -> Ordering { +//let a_stamp = a.created().unwrap_or_else(SystemTime::now); +//let b_stamp = b.created().unwrap_or_else(SystemTime::now); +//a_stamp.cmp(&b_stamp) +//} + +///// Comparator that sorts [Node]s by Creation timestamp, older to newer. +//pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { +//comparator(b, a) +//} +//} + +//pub mod modified { +//use crate::render::tree::node::Node; +//use core::cmp::Ordering; +//use std::time::SystemTime; + +///// Comparator that sorts [Node]s by Alteration timestamp, newer to older. +//pub fn comparator(a: &Node, b: &Node) -> Ordering { +//let a_stamp = a.modified().unwrap_or_else(SystemTime::now); +//let b_stamp = b.modified().unwrap_or_else(SystemTime::now); +//a_stamp.cmp(&b_stamp) +//} + +///// Comparator that sorts [Node]s by Alteration timestamp, older to newer. +//pub fn rev_comparator(a: &Node, b: &Node) -> Ordering { +//comparator(b, a) +//} +//} //} mod sizing { diff --git a/src/tree/node/display/mod.rs b/src/tree/node/display/mod.rs deleted file mode 100644 index e97296cc..00000000 --- a/src/tree/node/display/mod.rs +++ /dev/null @@ -1,153 +0,0 @@ -use crate::{ - ansi::Escaped, - context::Context, - tree::node::Node, -}; -use std::{ - borrow::Cow, - fmt::{self, Formatter}, -}; - -/// Helpers to prepare each invidual section of the [Node]'s attributes to display. -mod presenters; - -impl Node { - /// Formats the [Node] for the tree view. - #[cfg(unix)] - pub(super) fn tree( - &self, - f: &mut Formatter, - prefix: Option<&str>, - ctx: &Context, - ) -> fmt::Result { - let size = presenters::format_size(self, ctx); - let padded_icon = presenters::format_padded_icon(self, ctx); - let file_name = presenters::file_name(self); - - let pre = prefix.unwrap_or(""); - - let ln = if ctx.long { - let presenters::LongAttrs { - ino, - perms, - nlink, - blocks, - timestamp, - } = presenters::format_long(self, ctx); - - format!( - "{ino:::truncate(&ln, window_width); - write!(f, "{out}") - } else { - write!(f, "{ln}") - } - } - - /// Formats the [Node] for a plain report view. - #[cfg(unix)] - pub(super) fn flat(&self, f: &mut Formatter, ctx: &Context) -> fmt::Result { - use std::{ffi::OsStr, path::Path}; - - let size = presenters::format_size(self, ctx); - - let file = { - let node_path = self.path(); - - if self.depth() == 0 { - node_path.file_name().map_or_else( - || Cow::from(node_path.display().to_string()), - OsStr::to_string_lossy, - ) - } else { - node_path - .strip_prefix(ctx.dir_canonical()) - .map_or_else(|_| self.path().to_string_lossy(), Path::to_string_lossy) - } - }; - - let ln = if ctx.long { - let presenters::LongAttrs { - ino, - perms, - nlink, - blocks, - timestamp, - } = presenters::format_long(self, ctx); - - format!( - "{ino:::truncate(&ln, window_width); - writeln!(f, "{out}") - } else { - writeln!(f, "{ln}") - } - } - - /// Formats the [Node] for a plain report view. - #[cfg(not(unix))] - pub(super) fn flat(&self, f: &mut Formatter, ctx: &Context) -> fmt::Result { - let size = presenters::format_size(self, ctx); - - let file = { - let path = self - .path() - .strip_prefix(ctx.dir_canonical()) - .unwrap_or_else(|_| self.path()); - - Cow::from(path.display().to_string()) - }; - - let ln = format!("{size} {file}"); - - if ctx.truncate && ctx.window_width.is_some() { - let window_width = ctx.window_width.unwrap(); - let out = ::truncate(&ln, window_width); - writeln!(f, "{out}") - } else { - writeln!(f, "{ln}") - } - } - - /// Formats the [Node] for the tree view. - #[cfg(not(unix))] - pub(super) fn tree( - &self, - f: &mut Formatter, - prefix: Option<&str>, - ctx: &Context, - ) -> fmt::Result { - let size = presenters::format_size(self, ctx); - let padded_icon = presenters::format_padded_icon(self, ctx); - let file_name = presenters::file_name(self); - let pre = prefix.unwrap_or(""); - - let ln = format!("{size} {pre}{padded_icon}{file_name}"); - - if ctx.truncate && ctx.window_width.is_some() { - let window_width = ctx.window_width.unwrap(); - let out = ::truncate(&ln, window_width); - write!(f, "{out}") - } else { - write!(f, "{ln}") - } - } -} diff --git a/src/tree/node/display/presenters.rs b/src/tree/node/display/presenters.rs deleted file mode 100644 index b069f2b1..00000000 --- a/src/tree/node/display/presenters.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::{context::Context, disk_usage::file_size::FileSize, tree::Node}; -use std::borrow::Cow; - -#[cfg(unix)] -use crate::{ - context::time::Stamp, - styles::{self, error::Error, PLACEHOLDER}, -}; - -#[cfg(unix)] -use std::time::SystemTime; - -#[cfg(unix)] -type StyleGetter = fn() -> Result<&'static ansi_term::Style, Error<'static>>; - -/// Attributes for the long view to be displayed. -#[cfg(unix)] -pub(super) struct LongAttrs { - pub ino: String, - pub perms: String, - pub nlink: String, - pub blocks: String, - pub timestamp: String, -} - -/// Formats the parameters for the long view. -#[cfg(unix)] -#[inline] -pub(super) fn format_long(node: &Node, ctx: &Context) -> LongAttrs { - let file_mode = node.mode().unwrap(); - - let perms = if ctx.octal { - Node::style_octal_permissions(&file_mode) - } else if node.has_xattrs() { - Node::style_sym_permissions(&file_mode, true) - } else { - Node::style_sym_permissions(&file_mode, false) - }; - - let datetime = match ctx.time() { - Stamp::Created => node.created(), - Stamp::Accessed => node.accessed(), - Stamp::Modified => node.modified(), - }; - - let ino = format_num(node.ino(), ctx.max_ino_width, styles::get_ino_style); - let nlink = format_num(node.nlink(), ctx.max_nlink_width, styles::get_nlink_style); - let blocks = format_num(node.blocks(), ctx.max_block_width, styles::get_block_style); - let timestamp = format_datetime(datetime); - - LongAttrs { - ino, - perms, - nlink, - blocks, - timestamp, - } -} - -/// Builds the disk usage portion of the output. -#[inline] -pub(super) fn format_size(node: &Node, ctx: &Context) -> String { - node.file_size().map_or_else( - || FileSize::placeholder(ctx), - |size| size.format(ctx.max_size_width, ctx.max_size_unit_width), - ) -} - -/// Builds the icon portion of the output. -#[inline] -pub(super) fn format_padded_icon(node: &Node, ctx: &Context) -> String { - if ctx.icons { - let icon = node.compute_icon(ctx.no_color()); - let padding = icon.len() - 1; - format!("{icon:, max_width: usize, style_getter: StyleGetter) -> String { - let out = num - .map(|num| format!("{num:>max_width$}")) - .unwrap_or(format!("{PLACEHOLDER:>max_width$}")); - - if let Ok(style) = style_getter() { - style.paint(out).to_string() - } else { - out - } -} - -#[cfg(unix)] -#[inline] -pub(super) fn format_datetime(datetime: Option) -> String { - use chrono::{offset::Local, DateTime}; - - let out = datetime.map(DateTime::::from).map_or_else( - || format!("{PLACEHOLDER:>12}"), - |dt| format!("{:>12}", dt.format("%d %h %H:%M %g")), - ); - - if let Ok(style) = styles::get_datetime_style() { - style.paint(out).to_string() - } else { - out - } -} - -#[inline] -pub(super) fn file_name(node: &Node) -> Cow { - node.symlink_target_file_name().map_or_else( - || Node::stylize(node.file_name(), node.style), - |target_name| { - let link_name = node.file_name(); - Node::stylize_link_name(link_name, target_name, node.style) - }, - ) -} diff --git a/src/tree/node/mod.rs b/src/tree/node/mod.rs index 0d37868f..efe3118e 100644 --- a/src/tree/node/mod.rs +++ b/src/tree/node/mod.rs @@ -1,8 +1,8 @@ use crate::{ - fs::inode::Inode, - icons, context::Context, disk_usage::file_size::{DiskUsage, FileSize}, + fs::inode::Inode, + icons, styles::get_ls_colors, tree::error::Error, }; @@ -13,7 +13,6 @@ use std::{ borrow::Cow, convert::TryFrom, ffi::OsStr, - fmt::{self, Formatter}, fs::{FileType, Metadata}, path::{Path, PathBuf}, time::SystemTime, @@ -28,9 +27,6 @@ use crate::fs::{ /// Ordering and sorting rules for [Node]. pub mod cmp; -/// Concerned with formating [Node]s for display variants. -pub mod display; - /// Styling utilities for a [Node]. pub mod style; @@ -199,24 +195,17 @@ impl Node { /// Whether or not [Node] has extended attributes. #[cfg(unix)] - const fn has_xattrs(&self) -> bool { + pub const fn has_xattrs(&self) -> bool { self.has_xattrs } - /// Formats the [Node] for the tree presentation. - pub fn tree_display(&self, f: &mut Formatter, prefix: &str, ctx: &Context) -> fmt::Result { - self.tree(f, Some(prefix), ctx) - } - - /// Formats the [Node] for the [`Flat`] presentation. - /// - /// [`Flat`]: crate::tree::display::Flat - pub fn flat_display(&self, f: &mut Formatter, ctx: &Context) -> fmt::Result { - self.flat(f, ctx) + /// Getter for [Node]'s style field. + pub const fn style(&self) -> Option