Compare commits

..

10 Commits

Author SHA1 Message Date
andy.boot
07c228fd08 Fix: depth=0 bug for multiple arguments
https://github.com/bootandy/dust/issues/282
2023-01-05 01:11:16 +00:00
andy.boot
c457a295b5 fix: bug where hard links could be double counted
When running:
dust dir_a dir_b

if a file was hard linked in both dir_a and dir_b it would be double
counted.

This fix resolves this by keeping the shared hashmap around between runs
for the second and subsequent arguments.
https://github.com/bootandy/dust/issues/282
2023-01-04 22:22:41 +00:00
andy.boot
a91aa62060 clippy: Fix new clippy 2023-01-04 20:08:20 +00:00
andy.boot
a7b82f32d7 fix: update use of sysinfo.system
System is now much quicker to start but requires an explicit call
to refresh memory else it deafults to 0 (oops)
2023-01-04 20:08:20 +00:00
Guillaume Gomez
72b811c278 Update sysinfo version to 0.26.7 2023-01-04 19:38:40 +00:00
Phil Clifford
b478534b22 added deb-get as installation source 2022-10-24 23:25:13 +01:00
DIRE
2ca7177446 FEATURE: support only directories will be displayed. Flag -D 2022-10-05 23:26:10 +01:00
Collin Styles
e858f9e976 Read inputs from stdin when applicable 2022-10-01 11:16:14 +01:00
Collin Styles
0a67191054 Add a direct dependency on the atty crate 2022-10-01 11:16:14 +01:00
andy.boot
c363e5ff8b Fix: Only create large stack size if enough memory
Small boxes do not have enough memory to create a large stack

Conversely we want a large stack size for large boxes with a very highly
nested directory structure.

Version: New version
2022-08-31 21:14:56 +01:00
16 changed files with 129 additions and 57 deletions

17
Cargo.lock generated
View File

@@ -68,12 +68,6 @@ dependencies = [
"regex-automata",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -213,6 +207,7 @@ version = "0.8.3"
dependencies = [
"ansi_term",
"assert_cmd",
"atty",
"clap",
"clap_complete",
"config-file",
@@ -337,9 +332,9 @@ dependencies = [
[[package]]
name = "ntapi"
version = "0.3.7"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc"
dependencies = [
"winapi",
]
@@ -542,14 +537,12 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.15.9"
version = "0.26.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de94457a09609f33fec5e7fceaf907488967c6c7c75d64da6a7ce6ffdb8b5abd"
checksum = "c375d5fd899e32847b8566e10598d6e9f1d9b55ec6de3cdf9e7da4bdc51371bc"
dependencies = [
"cc",
"cfg-if",
"core-foundation-sys",
"doc-comment",
"libc",
"ntapi",
"once_cell",

View File

@@ -28,6 +28,7 @@ strip = true
[dependencies]
ansi_term = "0.12"
atty = "0.2.14"
clap = "3.2.17"
lscolors = "0.7"
terminal_size = "0.1"
@@ -39,7 +40,7 @@ regex = "1"
config-file = "0.2"
serde = { version = "1.0", features = ["derive"] }
directories = "4"
sysinfo = "0.15"
sysinfo = "0.26"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"

View File

@@ -30,6 +30,10 @@ Because I want an easy way to see where my disk is being used.
- `pacstall -I dust-bin`
#### [deb-get](https://github.com/wimpysworld/deb-get) (Debian/Ubuntu)
- `deb-get install du-dust`
#### Windows:
- Windows GNU version - works
@@ -59,6 +63,7 @@ Usage: dust -p (full-path - Show fullpath of the subdirectories)
Usage: dust -s (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 (Shows 30 directories instead of the default [default is terminal height])
Usage: dust -d 3 (Shows 3 levels of subdirectories)
Usage: dust -D (Show only directories (eg dust -D))
Usage: dust -r (reverse order of output)
Usage: dust -H (si print sizes in powers of 1000 instead of 1024)
Usage: dust -X ignore (ignore all files and directories with the name 'ignore')

View File

@@ -54,6 +54,8 @@ _dust() {
'(-d --depth)--file_types[show only these file types]' \
'-H[print sizes in powers of 1000 (e.g., 1.1G)]' \
'--si[print sizes in powers of 1000 (e.g., 1.1G)]' \
'-D[Only directories will be displayed.]' \
'--only-dir[Only directories will be displayed.]' \
'*::inputs:' \
&& ret=0
}

View File

@@ -60,6 +60,8 @@ Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
[CompletionResult]::new('--file_types', 'file_types', [CompletionResultType]::ParameterName, 'show only these file types')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'print sizes in powers of 1000 (e.g., 1.1G)')
[CompletionResult]::new('--si', 'si', [CompletionResultType]::ParameterName, 'print sizes in powers of 1000 (e.g., 1.1G)')
[CompletionResult]::new('-D', 'D', [CompletionResultType]::ParameterName, 'Only directories will be displayed.')
[CompletionResult]::new('--only-dir', 'only-dir', [CompletionResultType]::ParameterName, 'Only directories will be displayed.')
break
}
})

View File

@@ -19,7 +19,7 @@ _dust() {
case "${cmd}" in
dust)
opts="-h -V -d -n -p -X -x -s -r -c -b -z -f -i -v -e -t -w -H --help --version --depth --number-of-lines --full-paths --ignore-directory --limit-filesystem --apparent-size --reverse --no-colors --no-percent-bars --min-size --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --si <inputs>..."
opts="-h -V -d -n -p -X -x -s -r -c -b -z -f -i -v -e -t -w -H -D --help --version --depth --number-of-lines --full-paths --ignore-directory --limit-filesystem --apparent-size --reverse --no-colors --no-percent-bars --min-size --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --si --only-dir <inputs>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0

View File

@@ -57,6 +57,8 @@ set edit:completion:arg-completer[dust] = {|@words|
cand --file_types 'show only these file types'
cand -H 'print sizes in powers of 1000 (e.g., 1.1G)'
cand --si 'print sizes in powers of 1000 (e.g., 1.1G)'
cand -D 'Only directories will be displayed.'
cand --only-dir 'Only directories will be displayed.'
}
]
$completions[$command]

View File

@@ -18,3 +18,4 @@ complete -c dust -s f -l filecount -d 'Directory \'size\' is number of child fil
complete -c dust -s i -l ignore_hidden -d 'Do not display hidden files'
complete -c dust -s t -l file_types -d 'show only these file types'
complete -c dust -s H -l si -d 'print sizes in powers of 1000 (e.g., 1.1G)'
complete -c dust -s D -l only-dir -d 'Only directories will be displayed.'

View File

@@ -131,5 +131,11 @@ pub fn build_cli() -> Command<'static> {
.long("si")
.help("print sizes in powers of 1000 (e.g., 1.1G)")
)
.arg(Arg::new("inputs").multiple_occurrences(true).default_value("."))
.arg(Arg::new("inputs").multiple_occurrences(true))
.arg(
Arg::new("only_dir")
.short('D')
.long("only-dir")
.help("Only directories will be displayed."),
)
}

View File

@@ -19,6 +19,7 @@ pub struct Config {
pub ignore_hidden: Option<bool>,
pub iso: Option<bool>,
pub min_size: Option<String>,
pub only_dir: Option<bool>,
}
impl Config {
@@ -61,6 +62,9 @@ impl Config {
size_from_param
}
}
pub fn get_only_dir(&self, options: &ArgMatches) -> bool {
Some(true) == self.only_dir || options.is_present("only_dir")
}
}
fn convert_min_size(input: &str, iso: bool) -> Option<usize> {

View File

@@ -31,12 +31,13 @@ pub struct WalkData<'a> {
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> (Vec<Node>, bool) {
let permissions_flag = AtomicBool::new(false);
let mut inodes = HashSet::new();
let top_level_nodes: Vec<_> = dirs
.into_iter()
.filter_map(|d| {
clean_inodes(
walk(d, &permissions_flag, &walk_data, 0)?,
&mut HashSet::new(),
&mut inodes,
walk_data.use_apparent_size,
)
})

View File

@@ -88,7 +88,7 @@ impl DrawData<'_> {
let mut num_not_my_bar = (chars_in_bar as i32) - num_bars as i32;
let mut new_bar = "".to_string();
let idx = 5 - min(4, max(1, level));
let idx = 5 - level.clamp(1, 4);
for c in self.percent_bar.chars() {
num_not_my_bar -= 1;
@@ -141,10 +141,10 @@ pub fn draw_it(
let longest_string_length =
find_longest_dir_name(root_node, num_indent_chars, allowed_width, !use_full_path);
let max_bar_length = if no_percent_bars || longest_string_length + 7 >= allowed_width as usize {
let max_bar_length = if no_percent_bars || longest_string_length + 7 >= allowed_width {
0
} else {
allowed_width as usize - longest_string_length - 7
allowed_width - longest_string_length - 7
};
let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect();

View File

@@ -8,6 +8,7 @@ use std::path::PathBuf;
pub fn get_biggest(
top_level_nodes: Vec<Node>,
min_size: Option<usize>,
only_dir: bool,
n: usize,
depth: usize,
using_a_filter: bool,
@@ -19,18 +20,25 @@ pub fn get_biggest(
let mut heap = BinaryHeap::new();
let number_top_level_nodes = top_level_nodes.len();
let root = get_new_root(top_level_nodes);
let mut allowed_nodes = HashSet::new();
allowed_nodes.insert(root.name.as_path());
heap = add_children(using_a_filter, min_size, &root, depth, heap);
for _ in number_top_level_nodes..n {
if number_top_level_nodes > 1 {
heap = add_children(using_a_filter, min_size, only_dir, &root, usize::MAX, heap);
} else {
heap = add_children(using_a_filter, min_size, only_dir, &root, depth, heap);
}
let number_of_lines_in_output = n - number_top_level_nodes;
for _ in 0..number_of_lines_in_output {
let line = heap.pop();
match line {
Some(line) => {
allowed_nodes.insert(line.name.as_path());
heap = add_children(using_a_filter, min_size, line, depth, heap);
heap = add_children(using_a_filter, min_size, only_dir, line, depth, heap);
}
None => break,
}
@@ -41,15 +49,22 @@ pub fn get_biggest(
fn add_children<'a>(
using_a_filter: bool,
min_size: Option<usize>,
only_dir: bool,
file_or_folder: &'a Node,
depth: usize,
mut heap: BinaryHeap<&'a Node>,
) -> BinaryHeap<&'a Node> {
if depth > file_or_folder.depth {
heap.extend(file_or_folder.children.iter().filter(|c| match min_size {
Some(ms) => c.size > ms as u64,
None => !using_a_filter || c.name.is_file() || c.size > 0,
}))
heap.extend(
file_or_folder
.children
.iter()
.filter(|c| match min_size {
Some(ms) => c.size > ms as u64,
None => !using_a_filter || c.name.is_file() || c.size > 0,
})
.filter(|c| if only_dir { c.name.is_dir() } else { true }),
)
}
heap
}

View File

@@ -11,6 +11,7 @@ mod utils;
use crate::cli::build_cli;
use std::collections::HashSet;
use std::io::BufRead;
use std::process;
use sysinfo::{System, SystemExt};
@@ -91,14 +92,28 @@ fn get_regex_value(maybe_value: Option<Values>) -> Vec<Regex> {
.collect()
}
// Returns a list of lines from stdin or `None` if there's nothing to read
fn get_lines_from_stdin() -> Option<Vec<String>> {
atty::isnt(atty::Stream::Stdin).then(|| {
std::io::stdin()
.lock()
.lines()
.collect::<Result<_, _>>()
.expect("Error reading from stdin")
})
}
fn main() {
let options = build_cli().get_matches();
let config = get_config();
let stdin_lines = get_lines_from_stdin();
let target_dirs = options
.values_of("inputs")
.expect("Should be a default value here")
.collect();
let target_dirs = match options.values_of("inputs") {
Some(values) => values.collect(),
None => stdin_lines.as_ref().map_or(vec!["."], |lines| {
lines.iter().map(String::as_str).collect()
}),
};
let summarize_file_types = options.is_present("types");
@@ -166,6 +181,7 @@ fn main() {
false => get_biggest(
top_level_nodes,
config.get_min_size(&options, iso),
config.get_only_dir(&options),
number_of_lines,
depth,
options.values_of("filter").is_some() || options.value_of("invert_filter").is_some(),
@@ -192,9 +208,9 @@ fn main() {
fn init_rayon() -> Result<(), ThreadPoolBuildError> {
let large_stack = usize::pow(1024, 3);
// Warning: Creating System is slow, takes ~ 100ms
let s = System::new();
let available = s.get_available_memory() * 1024;
let mut s = System::new();
s.refresh_memory();
let available = s.available_memory();
if available > large_stack.try_into().unwrap() {
// Larger stack size to handle cases with lots of nested directories

View File

@@ -73,6 +73,7 @@ pub fn test_ignore_dir() {
let output = build_command(vec!["-c", "-X", "dir_substring", "tests/test_dir2/"]);
assert!(!output.contains("dir_substring"));
}
// Add test for multiple dirs - with -d 0 and maybe -d 1 check the
#[test]
pub fn test_with_bad_param() {

View File

@@ -17,6 +17,18 @@ fn build_temp_file(dir: &TempDir) -> PathBuf {
file_path
}
fn link_it(link_path: PathBuf, file_path_s: &str, is_soft: bool) -> String {
let link_name_s = link_path.to_str().unwrap();
let mut c = Command::new("ln");
if is_soft {
c.arg("-s");
}
c.arg(file_path_s);
c.arg(link_name_s);
assert!(c.output().is_ok());
return link_name_s.into();
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_soft_sym_link() {
@@ -26,13 +38,7 @@ pub fn test_soft_sym_link() {
let file_path_s = file.to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg("-s")
.arg(file_path_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let link_name_s = link_it(link_name, file_path_s, true);
let c = format!(" ├── {}", link_name_s);
let b = format!(" ┌── {}", file_path_s);
@@ -41,7 +47,7 @@ pub fn test_soft_sym_link() {
let mut cmd = Command::cargo_bin("dust").unwrap();
// Mac test runners create long filenames in tmp directories
let output = cmd
.args(["-p", "-c", "-s", "-w 999", dir_s])
.args(["-p", "-c", "-s", "-w", "999", dir_s])
.unwrap()
.stdout;
@@ -61,19 +67,14 @@ pub fn test_hard_sym_link() {
let file_path_s = file.to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg(file_path_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
link_it(link_name, file_path_s, false);
let file_output = format!(" ┌── {}", file_path_s);
let dirs_output = format!("─┴ {}", dir_s);
let mut cmd = Command::cargo_bin("dust").unwrap();
// Mac test runners create long filenames in tmp directories
let output = cmd.args(["-p", "-c", "-w 999", dir_s]).unwrap().stdout;
let output = cmd.args(["-p", "-c", "-w", "999", dir_s]).unwrap().stdout;
// The link should not appear in the output because multiple inodes are now ordered
// then filtered.
@@ -82,6 +83,34 @@ pub fn test_hard_sym_link() {
assert!(output.contains(file_output.as_str()));
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_hard_sym_link_no_dup_multi_arg() {
let dir = Builder::new().tempdir().unwrap();
let dir_link = Builder::new().tempdir().unwrap();
let file = build_temp_file(&dir);
let dir_s = dir.path().to_str().unwrap();
let dir_link_s = dir_link.path().to_str().unwrap();
let file_path_s = file.to_str().unwrap();
let link_name = dir_link.path().join("the_link");
let link_name_s = link_it(link_name, file_path_s, false);
let mut cmd = Command::cargo_bin("dust").unwrap();
// Mac test runners create long filenames in tmp directories
let output = cmd
.args(["-p", "-c", "-w", "999", "-b", dir_link_s, dir_s])
.unwrap()
.stdout;
// The link or the file should appear but not both
let output = str::from_utf8(&output).unwrap();
let has_file_only = output.contains(file_path_s) && !output.contains(&link_name_s);
let has_link_only = !output.contains(file_path_s) && output.contains(&link_name_s);
assert!(has_file_only || has_link_only);
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_recursive_sym_link() {
@@ -89,14 +118,7 @@ pub fn test_recursive_sym_link() {
let dir_s = dir.path().to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg("-s")
.arg(dir_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let link_name_s = link_it(link_name, dir_s, true);
let a = format!("─┬ {}", dir_s);
let b = format!(" └── {}", link_name_s);
@@ -107,7 +129,8 @@ pub fn test_recursive_sym_link() {
.arg("-c")
.arg("-r")
.arg("-s")
.arg("-w 999")
.arg("-w")
.arg("999")
.arg(dir_s)
.unwrap()
.stdout;