mirror of
https://github.com/bootandy/dust.git
synced 2026-06-08 11:29:05 +03:00
Feature: Filter by invert_filter: reverse match
Mimic grep's -v option. Allows dust to only match files that do not match the given filter
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
|
use crate::utils::is_filtered_out_due_to_invert_regex;
|
||||||
use crate::utils::is_filtered_out_due_to_regex;
|
use crate::utils::is_filtered_out_due_to_regex;
|
||||||
use rayon::iter::ParallelBridge;
|
use rayon::iter::ParallelBridge;
|
||||||
use rayon::prelude::ParallelIterator;
|
use rayon::prelude::ParallelIterator;
|
||||||
@@ -20,6 +21,7 @@ use crate::platform::get_metadata;
|
|||||||
pub struct WalkData {
|
pub struct WalkData {
|
||||||
pub ignore_directories: HashSet<PathBuf>,
|
pub ignore_directories: HashSet<PathBuf>,
|
||||||
pub filter_regex: Option<Regex>,
|
pub filter_regex: Option<Regex>,
|
||||||
|
pub invert_filter_regex: Option<Regex>,
|
||||||
pub allowed_filesystems: HashSet<u64>,
|
pub allowed_filesystems: HashSet<u64>,
|
||||||
pub use_apparent_size: bool,
|
pub use_apparent_size: bool,
|
||||||
pub by_filecount: bool,
|
pub by_filecount: bool,
|
||||||
@@ -96,6 +98,13 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if walk_data.invert_filter_regex.is_some()
|
||||||
|
&& entry.path().is_file()
|
||||||
|
&& is_filtered_out_due_to_invert_regex(&walk_data.invert_filter_regex, &entry.path())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
(is_dot_file && walk_data.ignore_hidden) || is_ignored_path
|
(is_dot_file && walk_data.ignore_hidden) || is_ignored_path
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +132,7 @@ fn walk(dir: PathBuf, permissions_flag: &AtomicBool, walk_data: &WalkData) -> Op
|
|||||||
entry.path(),
|
entry.path(),
|
||||||
vec![],
|
vec![],
|
||||||
&walk_data.filter_regex,
|
&walk_data.filter_regex,
|
||||||
|
&walk_data.invert_filter_regex,
|
||||||
walk_data.use_apparent_size,
|
walk_data.use_apparent_size,
|
||||||
data.is_symlink(),
|
data.is_symlink(),
|
||||||
data.is_file(),
|
data.is_file(),
|
||||||
@@ -143,6 +153,7 @@ fn walk(dir: PathBuf, permissions_flag: &AtomicBool, walk_data: &WalkData) -> Op
|
|||||||
dir,
|
dir,
|
||||||
children,
|
children,
|
||||||
&walk_data.filter_regex,
|
&walk_data.filter_regex,
|
||||||
|
&walk_data.invert_filter_regex,
|
||||||
walk_data.use_apparent_size,
|
walk_data.use_apparent_size,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
|||||||
+5
-5
@@ -17,7 +17,7 @@ pub fn get_by_depth(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode>
|
|||||||
pub fn get_biggest(
|
pub fn get_biggest(
|
||||||
top_level_nodes: Vec<Node>,
|
top_level_nodes: Vec<Node>,
|
||||||
n: usize,
|
n: usize,
|
||||||
using_file_type_filter: bool,
|
using_a_filter: bool,
|
||||||
) -> Option<DisplayNode> {
|
) -> Option<DisplayNode> {
|
||||||
if top_level_nodes.is_empty() {
|
if top_level_nodes.is_empty() {
|
||||||
// perhaps change this, bring back Error object?
|
// perhaps change this, bring back Error object?
|
||||||
@@ -30,14 +30,14 @@ pub fn get_biggest(
|
|||||||
let mut allowed_nodes = HashSet::new();
|
let mut allowed_nodes = HashSet::new();
|
||||||
|
|
||||||
allowed_nodes.insert(&root.name);
|
allowed_nodes.insert(&root.name);
|
||||||
heap = add_children(using_file_type_filter, &root, heap);
|
heap = add_children(using_a_filter, &root, heap);
|
||||||
|
|
||||||
for _ in number_top_level_nodes..n {
|
for _ in number_top_level_nodes..n {
|
||||||
let line = heap.pop();
|
let line = heap.pop();
|
||||||
match line {
|
match line {
|
||||||
Some(line) => {
|
Some(line) => {
|
||||||
allowed_nodes.insert(&line.name);
|
allowed_nodes.insert(&line.name);
|
||||||
heap = add_children(using_file_type_filter, line, heap);
|
heap = add_children(using_a_filter, line, heap);
|
||||||
}
|
}
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
@@ -76,11 +76,11 @@ pub fn get_all_file_types(top_level_nodes: Vec<Node>, n: usize) -> Option<Displa
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_children<'a>(
|
fn add_children<'a>(
|
||||||
using_file_type_filter: bool,
|
using_a_filter: bool,
|
||||||
line: &'a Node,
|
line: &'a Node,
|
||||||
mut heap: BinaryHeap<&'a Node>,
|
mut heap: BinaryHeap<&'a Node>,
|
||||||
) -> BinaryHeap<&'a Node> {
|
) -> BinaryHeap<&'a Node> {
|
||||||
if using_file_type_filter {
|
if using_a_filter {
|
||||||
line.children.iter().for_each(|c| {
|
line.children.iter().for_each(|c| {
|
||||||
if c.name.is_file() || c.size > 0 {
|
if c.name.is_file() || c.size > 0 {
|
||||||
heap.push(c)
|
heap.push(c)
|
||||||
|
|||||||
+32
-13
@@ -73,6 +73,19 @@ fn get_width_of_terminal() -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_regex_value(maybe_value: Option<&str>) -> Option<Regex> {
|
||||||
|
match maybe_value {
|
||||||
|
Some(v) => match Regex::new(v) {
|
||||||
|
Ok(r) => Some(r),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Ignoring bad value for regex {:?}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let default_height = get_height_of_terminal();
|
let default_height = get_height_of_terminal();
|
||||||
let def_num_str = default_height.to_string();
|
let def_num_str = default_height.to_string();
|
||||||
@@ -151,9 +164,21 @@ fn main() {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("ignore_hidden")
|
Arg::with_name("ignore_hidden")
|
||||||
.short("i") // Do not use 'h' this is used by 'help'
|
.short("i") // Do not use 'h' this is used by 'help'
|
||||||
.long("ignore_hidden")
|
.long("ignore_hidden") //TODO: fix change - -> _
|
||||||
.help("Do not display hidden files"),
|
.help("Do not display hidden files"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("invert_filter")
|
||||||
|
.short("v")
|
||||||
|
.long("invert-filter")
|
||||||
|
.takes_value(true)
|
||||||
|
.number_of_values(1)
|
||||||
|
.multiple(true)
|
||||||
|
.conflicts_with("filter")
|
||||||
|
.conflicts_with("types")
|
||||||
|
.conflicts_with("depth")
|
||||||
|
.help("Exclude files matching this regex. To ignore png files type: -v \"\\.png$\" "),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("filter")
|
Arg::with_name("filter")
|
||||||
.short("e")
|
.short("e")
|
||||||
@@ -162,6 +187,7 @@ fn main() {
|
|||||||
.number_of_values(1)
|
.number_of_values(1)
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.conflicts_with("types")
|
.conflicts_with("types")
|
||||||
|
.conflicts_with("depth")
|
||||||
.help("Only include files matching this regex. For png files type: -e \"\\.png$\" "),
|
.help("Only include files matching this regex. For png files type: -e \"\\.png$\" "),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@@ -191,17 +217,8 @@ fn main() {
|
|||||||
|
|
||||||
let summarize_file_types = options.is_present("types");
|
let summarize_file_types = options.is_present("types");
|
||||||
|
|
||||||
let maybe_filter = if options.is_present("filter") {
|
let maybe_filter = get_regex_value(options.value_of("filter"));
|
||||||
match Regex::new(options.value_of("filter").unwrap()) {
|
let maybe_invert_filter = get_regex_value(options.value_of("invert_filter"));
|
||||||
Ok(r) => Some(r),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Ignoring bad value for filter {:?}", e);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let number_of_lines = match value_t!(options.value_of("number_of_lines"), usize) {
|
let number_of_lines = match value_t!(options.value_of("number_of_lines"), usize) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
@@ -252,6 +269,7 @@ fn main() {
|
|||||||
let walk_data = WalkData {
|
let walk_data = WalkData {
|
||||||
ignore_directories: ignored_full_path,
|
ignore_directories: ignored_full_path,
|
||||||
filter_regex: maybe_filter,
|
filter_regex: maybe_filter,
|
||||||
|
invert_filter_regex: maybe_invert_filter,
|
||||||
allowed_filesystems,
|
allowed_filesystems,
|
||||||
use_apparent_size,
|
use_apparent_size,
|
||||||
by_filecount,
|
by_filecount,
|
||||||
@@ -267,7 +285,8 @@ fn main() {
|
|||||||
(_, _) => get_biggest(
|
(_, _) => get_biggest(
|
||||||
top_level_nodes,
|
top_level_nodes,
|
||||||
number_of_lines,
|
number_of_lines,
|
||||||
options.values_of("filter").is_some(),
|
options.values_of("filter").is_some()
|
||||||
|
|| options.value_of("invert_filter").is_some(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::platform::get_metadata;
|
use crate::platform::get_metadata;
|
||||||
|
use crate::utils::is_filtered_out_due_to_invert_regex;
|
||||||
use crate::utils::is_filtered_out_due_to_regex;
|
use crate::utils::is_filtered_out_due_to_regex;
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -13,10 +14,12 @@ pub struct Node {
|
|||||||
pub inode_device: Option<(u64, u64)>,
|
pub inode_device: Option<(u64, u64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn build_node(
|
pub fn build_node(
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
children: Vec<Node>,
|
children: Vec<Node>,
|
||||||
filter_regex: &Option<Regex>,
|
filter_regex: &Option<Regex>,
|
||||||
|
invert_filter_regex: &Option<Regex>,
|
||||||
use_apparent_size: bool,
|
use_apparent_size: bool,
|
||||||
is_symlink: bool,
|
is_symlink: bool,
|
||||||
is_file: bool,
|
is_file: bool,
|
||||||
@@ -31,6 +34,7 @@ pub fn build_node(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let size = if is_filtered_out_due_to_regex(filter_regex, &dir)
|
let size = if is_filtered_out_due_to_regex(filter_regex, &dir)
|
||||||
|
|| is_filtered_out_due_to_invert_regex(invert_filter_regex, &dir)
|
||||||
|| (is_symlink && !use_apparent_size)
|
|| (is_symlink && !use_apparent_size)
|
||||||
|| by_filecount && !is_file
|
|| by_filecount && !is_file
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ pub fn is_filtered_out_due_to_regex(filter_regex: &Option<Regex>, dir: &Path) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_filtered_out_due_to_invert_regex(filter_regex: &Option<Regex>, dir: &Path) -> bool {
|
||||||
|
match filter_regex {
|
||||||
|
Some(fr) => fr.is_match(&dir.as_os_str().to_string_lossy()),
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
|
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
|
||||||
let parent = parent.as_ref();
|
let parent = parent.as_ref();
|
||||||
let child = child.as_ref();
|
let child = child.as_ref();
|
||||||
|
|||||||
+18
-3
@@ -129,14 +129,29 @@ pub fn test_show_files_by_type() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_show_files_by_specific_type() {
|
pub fn test_show_files_by_regex() {
|
||||||
// Check we can see '.rs' files in the tests directory
|
// Check we can see '.rs' files in the tests directory
|
||||||
let output = build_command(vec!["-c", "-e", "\\.rs$", "tests"]);
|
let output = build_command(vec!["-c", "-e", "\\.rs$", "tests"]);
|
||||||
assert!(output.contains(" ┌─┴ tests"));
|
assert!(output.contains(" ┌─┴ tests"));
|
||||||
assert!(!output.contains("0B ┌── tests"));
|
assert!(!output.contains("0B ┌── tests"));
|
||||||
assert!(!output.contains("0B ┌─┴ tests"));
|
assert!(!output.contains("0B ┌─┴ tests"));
|
||||||
|
|
||||||
// Check there are no '.bad_type' files in the tests directory
|
// Check there are no files named: '.match_nothing' in the tests directory
|
||||||
let output = build_command(vec!["-c", "-e", "bad_regex", "tests"]);
|
let output = build_command(vec!["-c", "-e", "match_nothing$", "tests"]);
|
||||||
assert!(output.contains("0B ┌── tests"));
|
assert!(output.contains("0B ┌── tests"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_show_files_by_invert_regex() {
|
||||||
|
let output = build_command(vec!["-c", "-f", "-v", "e", "tests/test_dir2"]);
|
||||||
|
// There are 0 files without 'e' in the name
|
||||||
|
assert!(output.contains("0 ┌── test_dir2"));
|
||||||
|
|
||||||
|
let output = build_command(vec!["-c", "-f", "-v", "a", "tests/test_dir2"]);
|
||||||
|
// There are 2 files without 'a' in the name
|
||||||
|
assert!(output.contains("2 ┌─┴ test_dir2"));
|
||||||
|
|
||||||
|
// There are 4 files in the test_dir2 hierarchy
|
||||||
|
let output = build_command(vec!["-c", "-f", "-v", "match_nothing$", "tests/test_dir2"]);
|
||||||
|
assert!(output.contains("4 ┌─┴ test_dir2"));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user