From 64c3742ab3eb72ff24c4d69f49e5f657979c03eb Mon Sep 17 00:00:00 2001 From: Benjamin Nguyen Date: Sat, 2 Dec 2023 13:08:34 -0800 Subject: [PATCH] globbing for tree layouts --- Cargo.lock | 159 +++++++++++++++++++--------------------- Cargo.toml | 2 +- src/file/tree/filter.rs | 90 ++++++++++++++++++++--- src/user/mod.rs | 38 +++++++--- 4 files changed, 185 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3361571..2647850 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,54 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -62,7 +110,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn", ] [[package]] @@ -119,17 +167,24 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.9" +version = "4.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9d6ada83c1edcce028902ea27dd929069c70df4c7600b131b4d9a1ad2879cc" +checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "is-terminal", - "once_cell", "strsim", - "termcolor", ] [[package]] @@ -143,25 +198,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.1.9" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "clap_lex" -version = "0.3.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" -dependencies = [ - "os_str_bytes", -] +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "config" @@ -433,18 +490,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "is-terminal" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix 0.37.7", - "windows-sys 0.45.0", -] - [[package]] name = "js-sys" version = "0.3.64" @@ -585,12 +630,6 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - [[package]] name = "overload" version = "0.1.1" @@ -626,30 +665,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.69" @@ -811,17 +826,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.38" @@ -846,15 +850,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.2.6" @@ -882,7 +877,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn", ] [[package]] @@ -980,7 +975,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn", "wasm-bindgen-shared", ] @@ -1002,7 +997,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1217,5 +1212,5 @@ checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index d372746..c1fd2f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ ahash = "0.8.6" ansi_term = "0.12.1" anyhow = "1.0.75" chrono = { version = "0.4.24", default-features = false, features = ["clock", "std"] } -clap = { version = "4.1.1", features = ["derive"] } +clap = { version = "4.4.10", features = ["derive"] } clap_complete = "4.1.1" config = { version = "0.13.3", default-features = false, features = ["toml"] } crossterm = "0.26.1" diff --git a/src/file/tree/filter.rs b/src/file/tree/filter.rs index 965d09c..43d54d4 100644 --- a/src/file/tree/filter.rs +++ b/src/file/tree/filter.rs @@ -3,11 +3,12 @@ use crate::{ file::{tree::Tree, File}, user::{ args::{FileType, Layout}, - Context, + Context, Globbing, }, }; use ahash::HashSet; -use indextree::{NodeEdge, NodeId}; +use ignore::overrides::OverrideBuilder; +use indextree::NodeId; use regex::Regex; #[derive(Debug, thiserror::Error)] @@ -31,7 +32,13 @@ impl Tree { } if ctx.pattern.is_some() { - self.filter_regex(ctx)?; + let Globbing { glob, iglob } = ctx.globbing; + + if glob || iglob { + self.filter_glob(ctx)?; + } else { + self.filter_regex(ctx)?; + } } if ctx.prune { @@ -48,14 +55,9 @@ impl Tree { while pruning { let mut to_remove = vec![]; - for node_edge in self.root_id.traverse(&self.arena) { - match node_edge { - NodeEdge::Start(_) => continue, - NodeEdge::End(n) => { - if self.arena[n].get().is_dir() && n.children(&self.arena).count() == 0 { - to_remove.push(n); - } - }, + for n in self.root_id.descendants(&self.arena) { + if self.arena[n].get().is_dir() && n.children(&self.arena).count() == 0 { + to_remove.push(n); } } @@ -65,7 +67,6 @@ impl Tree { .for_each(|n| n.remove_subtree(&mut self.arena)); continue; } - pruning = false; } } @@ -115,6 +116,7 @@ impl Tree { .for_each(|n| n.remove_subtree(&mut self.arena)); } + /// Remove nodes/sub-trees that don't match the provided regular expression, `pattern`. pub fn filter_regex( &mut self, Context { @@ -154,4 +156,68 @@ impl Tree { Ok(()) } + + fn filter_glob(&mut self, ctx: &Context) -> Result<()> { + let Context { + globbing: Globbing { iglob, .. }, + pattern, + .. + } = ctx; + + let dir = ctx.dir_canonical()?; + let mut override_builder = OverrideBuilder::new(dir); + + let mut negated_glob = false; + + let overrides = { + if *iglob { + override_builder + .case_insensitive(true) + .into_report(ErrorCategory::Internal) + .context(error_source!())?; + } + + if let Some(ref glob) = pattern { + let trim = glob.trim_start(); + negated_glob = trim.starts_with('!'); + + if negated_glob { + override_builder + .add(trim.trim_start_matches('!')) + .into_report(ErrorCategory::Internal) + .context(error_source!())?; + } else { + override_builder + .add(trim) + .into_report(ErrorCategory::Internal) + .context(error_source!())?; + } + } + + override_builder.build().into_report(ErrorCategory::User)? + }; + + let no_match = |node_id: &NodeId| { + let dirent = self.arena[*node_id].get(); + + if dirent.is_dir() { + false + } else { + let matched = overrides.matched(dirent.path(), dirent.is_dir()); + !(negated_glob ^ matched.is_whitelist()) + } + }; + + let to_remove = self + .root_id + .descendants(&self.arena) + .filter(no_match) + .collect::>(); + + to_remove + .into_iter() + .for_each(|n| n.remove_subtree(&mut self.arena)); + + Ok(()) + } } diff --git a/src/user/mod.rs b/src/user/mod.rs index 71b258a..aa4fe0d 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -1,5 +1,5 @@ use crate::error::prelude::*; -use clap::Parser; +use clap::{Args, Parser}; use std::{env, fs, path::PathBuf}; /// Enum definitions for enumerated command-line arguments. @@ -56,37 +56,42 @@ pub struct Context { /// Show extended metadata and attributes #[cfg(unix)] - #[arg(short, long)] + #[arg(short, long, group = "ls-long")] pub long: bool, /// Show file's groups #[cfg(unix)] - #[arg(long)] + #[arg(long, requires = "ls-long")] pub group: bool, /// Show each file's ino #[cfg(unix)] - #[arg(long)] + #[arg(long, requires = "ls-long")] pub ino: bool, /// Show the total number of hardlinks to the underlying inode #[cfg(unix)] - #[arg(long)] + #[arg(long, requires = "ls-long")] pub nlink: bool, /// Show permissions in numeric octal format instead of symbolic #[cfg(unix)] - #[arg(long, requires = "long")] + #[arg(long, requires = "ls-long")] pub octal: bool, /// Which kind of timestamp to use #[cfg(unix)] - #[arg(long, value_enum, requires = "long", default_value_t)] + #[arg(long, value_enum, requires = "ls-long", default_value_t)] pub time: args::TimeStamp, /// Which format to use for the timestamp; default by default #[cfg(unix)] - #[arg(long = "time-format", value_enum, requires = "long", default_value_t)] + #[arg( + long = "time-format", + requires = "ls-long", + value_enum, + default_value_t + )] pub time_format: args::TimeFormat, /// Maximum depth to display @@ -99,9 +104,12 @@ pub struct Context { /// Regular expression (or glob if '--glob' or '--iglob' is used) used to match files by their /// relative path - #[arg(short, long)] + #[arg(short, long, group = "searching")] pub pattern: Option, + #[command(flatten)] + pub globbing: Globbing, + /// Omit empty directories from the output #[arg(short = 'P', long)] pub prune: bool, @@ -141,6 +149,18 @@ pub struct Context { pub column_metadata: column::Metadata, } +#[derive(Args, Debug)] +#[group(multiple = false)] +pub struct Globbing { + /// Enables glob based searching instead of regular expressions + #[arg(long, requires = "searching")] + pub glob: bool, + + /// Enables case-insensitive glob based searching instead of regular expressions + #[arg(long, requires = "searching")] + pub iglob: bool, +} + impl Context { pub fn init() -> Result { let mut clargs = Self::parse();