Skip to content

Commit

Permalink
filter by file type
Browse files Browse the repository at this point in the history
  • Loading branch information
solidiquis committed Nov 30, 2023
1 parent 867859b commit 6a9e95b
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
.DS_Store
foobar
51 changes: 49 additions & 2 deletions src/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ use std::{
path::{Path, PathBuf},
};

#[cfg(unix)]
use crate::file::unix::permissions::{file_type::FileType, SymbolicNotation};

/// Concerned with querying information about a file's underlying inode.
pub mod inode;
use inode::{INodeError, Inode};
Expand Down Expand Up @@ -87,8 +90,12 @@ impl File {
) -> Result<Self, io::Error> {
let path = data.path();

let (symlink_target, metadata) = if *follow {
(fs::read_link(path).ok(), fs::metadata(path)?)
let (symlink_target, metadata) = if data.file_type().is_some_and(|ft| ft.is_symlink()) {
if *follow {
(fs::read_link(path).ok(), fs::metadata(path)?)
} else {
(fs::read_link(path).ok(), fs::symlink_metadata(path)?)
}
} else {
(None, fs::symlink_metadata(path)?)
};
Expand Down Expand Up @@ -179,9 +186,49 @@ impl File {
.map(|dt| format!("{dt}"))
}

#[cfg(unix)]
pub fn is_fifo(&self) -> bool {
self.metadata()
.permissions()
.try_mode_symbolic_notation()
.map_or(false, |mode| mode.file_type() == &FileType::Fifo)
}

#[cfg(unix)]
pub fn is_socket(&self) -> bool {
self.metadata()
.permissions()
.try_mode_symbolic_notation()
.map_or(false, |mode| mode.file_type() == &FileType::Socket)
}

#[cfg(unix)]
pub fn is_char_device(&self) -> bool {
self.metadata()
.permissions()
.try_mode_symbolic_notation()
.map_or(false, |mode| mode.file_type() == &FileType::CharDevice)
}

#[cfg(unix)]
pub fn is_block_device(&self) -> bool {
self.metadata()
.permissions()
.try_mode_symbolic_notation()
.map_or(false, |mode| mode.file_type() == &FileType::BlockDevice)
}

pub fn is_dir(&self) -> bool {
self.file_type().is_some_and(|ft| ft.is_dir())
}

pub fn is_file(&self) -> bool {
self.file_type().is_some_and(|ft| ft.is_file())
}

pub fn is_symlink(&self) -> bool {
self.file_type().is_some_and(|ft| ft.is_symlink())
}
}

impl Deref for File {
Expand Down
61 changes: 61 additions & 0 deletions src/file/tree/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::{
file::{tree::Tree, File},
user::{
args::{FileType, Layout},
Context,
},
};
use ahash::HashSet;
use indextree::NodeId;

/// Predicate used for filtering a [`File`] based on [`FileType`].
pub type FileTypeFilter = dyn Fn(&File) -> bool;

impl Tree {
/// Updates the [`Tree`]'s inner [`indextree::Arena`] to only contain files of certain
/// file-types.
pub fn filter_file_type(
&mut self,
Context {
layout, file_type, ..
}: &Context,
) {
if file_type.is_empty() {
return;
}

let mut filters = Vec::<Box<FileTypeFilter>>::new();

for ft in HashSet::from_iter(file_type) {
match ft {
FileType::Dir if matches!(layout, Layout::Tree | Layout::InvertedTree) => {
filters.push(Box::new(|f| f.is_dir()))
},
FileType::Dir => filters.push(Box::new(|f| f.is_dir())),
FileType::File => filters.push(Box::new(|f| f.is_file())),
FileType::Symlink => filters.push(Box::new(|f| f.is_symlink())),

#[cfg(unix)]
FileType::Pipe => filters.push(Box::new(|f| f.is_fifo())),
#[cfg(unix)]
FileType::Socket => filters.push(Box::new(|f| f.is_socket())),
#[cfg(unix)]
FileType::Char => filters.push(Box::new(|f| f.is_char_device())),
#[cfg(unix)]
FileType::Block => filters.push(Box::new(|f| f.is_block_device())),
}
}

let no_match = |node_id: &NodeId| !filters.iter().any(|f| f(self.arena[*node_id].get()));

let to_remove = self
.root_id
.descendants(&self.arena)
.filter(no_match)
.collect::<Vec<_>>();

to_remove
.into_iter()
.for_each(|n| n.detach(&mut self.arena));
}
}
5 changes: 4 additions & 1 deletion src/file/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use ahash::{HashMap, HashSet};
use indextree::{Arena, NodeId};
use std::{ops::Deref, path::PathBuf};

/// Parallel disk reading
/// Concerned with filtering via file-type, globbing, and regular expressions.
mod filter;

/// Parallel disk reading.
mod traverse;

/// Representation of the file-tree that is traversed starting from the root directory whose index
Expand Down
2 changes: 1 addition & 1 deletion src/file/tree/traverse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ mod walker {
let walker = builder
.follow_links(ctx.follow)
.git_ignore(ctx.gitignore)
.git_global(ctx.gitignore)
.git_global(ctx.global_gitignore)
.threads(ctx.threads)
.hidden(ctx.no_hidden)
.same_file_system(ctx.same_fs);
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ fn run() -> Result<()> {
file_tree.prune();
}

if !ctx.file_type.is_empty() {
file_tree.filter_file_type(&ctx)
}

let output = render::output(&file_tree, &ctx)?;

let mut stdout = stdout().lock();
Expand Down
36 changes: 34 additions & 2 deletions src/user/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,12 @@ pub enum Sort {
/// Whether to sort directory entries relative either to their siblings or all directory entries
#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum SortType {
/// Sort directory entries relative to their siblings
/// Sort directory entries relative to their siblings. Only applies when using 'tree' or
/// 'inverted-tree' layout.
#[default]
Tree,
/// Sort directory entries relative to all directory entries
/// Sort directory entries relative to all directory entries. Only applies when using 'flat'
/// layout.
Flat,
}

Expand All @@ -151,3 +153,33 @@ pub enum DirOrder {
/// Sort directories below files
Last,
}

/// File-types found in both Unix and Windows.
#[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub enum FileType {
/// A regular file
#[default]
File,

/// A directory
Dir,

/// A symlink
Symlink,

/// A FIFO
#[cfg(unix)]
Pipe,

/// A socket
#[cfg(unix)]
Socket,

/// A character device
#[cfg(unix)]
Char,

/// A block device
#[cfg(unix)]
Block,
}
36 changes: 24 additions & 12 deletions src/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,30 @@ pub struct Context {
#[arg(long)]
pub no_git: bool,

/// Ignore files in .gitignore
#[arg(short = 'i', long)]
pub gitignore: bool,

/// Report byte size in either binary or SI units
#[arg(short, long, value_enum, default_value_t)]
pub byte_units: args::BytePresentation,

/// Sort directories before or after all other file types
#[arg(short, long, value_enum, default_value_t)]
pub dir_order: args::DirOrder,

/// Filter for specified file types
#[arg(short = 'F', long, value_enum)]
pub file_type: Vec<args::FileType>,

/// Follow symlinks
#[arg(short = 'f', long)]
pub follow: bool,

/// Ignore files that match rules in all .gitignore files encountered during traversal
#[arg(short = 'i', long)]
pub gitignore: bool,

/// Ignore files that match rules in the global .gitignore file
#[arg(long)]
pub global_gitignore: bool,

/// Show extended metadata and attributes
#[cfg(unix)]
#[arg(short, long)]
Expand Down Expand Up @@ -85,8 +97,12 @@ pub struct Context {
#[arg(short, long, value_enum, default_value_t)]
pub metric: args::Metric,

/// Omit empty directories from the output
/// Regular expression (or glob if '--glob' or '--iglob' is used) used to match files
#[arg(short, long)]
pub pattern: Option<String>,

/// Omit empty directories from the output
#[arg(short = 'P', long)]
pub prune: bool,

/// Field whereby to sort entries
Expand All @@ -97,9 +113,9 @@ pub struct Context {
#[arg(long, value_enum, default_value_t)]
pub sort_type: args::SortType,

/// Sort directories before or after all other file types
#[arg(short, long, value_enum, default_value_t)]
pub dir_order: args::DirOrder,
/// Don't compute disk-usage and omit file size from output
#[arg(short = 'S', long)]
pub suppress_size: bool,

/// Which kind of layout to use when rendering the output
#[arg(short = 'y', long, value_enum, default_value_t)]
Expand All @@ -113,10 +129,6 @@ pub struct Context {
#[arg(short = 'x', long = "one-file-system")]
pub same_fs: bool,

/// Don't compute disk-usage and omit file size from output
#[arg(long)]
pub suppress_size: bool,

/// Prints logs at the end of the output
#[arg(short = 'v', long = "verbose")]
pub verbose: bool,
Expand Down

0 comments on commit 6a9e95b

Please sign in to comment.