Compare commits

..

2 Commits

Author SHA1 Message Date
andy.boot
f92a97edc2 hack 2025-01-29 23:46:17 +00:00
andy.boot
6b83281183 hack 2025-01-27 23:40:48 +00:00
30 changed files with 1389 additions and 1718 deletions

851
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
[package] [package]
name = "du-dust" name = "du-dust"
description = "A more intuitive version of du" description = "A more intuitive version of du"
version = "1.2.3" version = "1.1.1"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"] authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2024" edition = "2021"
readme = "README.md" readme = "README.md"
documentation = "https://github.com/bootandy/dust" documentation = "https://github.com/bootandy/dust"
@@ -28,10 +28,10 @@ strip = true
[dependencies] [dependencies]
ansi_term = "0.12" ansi_term = "0.12"
clap = { version = "4", features = ["derive"] } clap = "4.4"
lscolors = "0.21" lscolors = "0.13"
terminal_size = "0.4" terminal_size = "0.2"
unicode-width = "0.2" unicode-width = "0.1"
rayon = "1" rayon = "1"
thousands = "0.2" thousands = "0.2"
stfu8 = "0.2" stfu8 = "0.2"
@@ -39,8 +39,9 @@ regex = "1"
config-file = "0.2" config-file = "0.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
sysinfo = "0.37" directories = "4"
ctrlc = "3" sysinfo = "0.27"
ctrlc = "3.4"
chrono = "0.4" chrono = "0.4"
[target.'cfg(not(target_has_atomic = "64"))'.dependencies] [target.'cfg(not(target_has_atomic = "64"))'.dependencies]
@@ -55,7 +56,7 @@ assert_cmd = "2"
tempfile = "=3" tempfile = "=3"
[build-dependencies] [build-dependencies]
clap = { version = "4.4", features = ["derive"] } clap = "4.4"
clap_complete = "4.4" clap_complete = "4.4"
clap_mangen = "0.2" clap_mangen = "0.2"

View File

@@ -13,16 +13,6 @@ Because I want an easy way to see where my disk is being used.
![Example](media/snap.png) ![Example](media/snap.png)
Study the above picture.
* We see `target` has 1.5G
* `target/debug` is the same size as `target` - so we know nearly all the disk usage of the 1.5G is in this folder
* `target/debug/deps` this is 1.0G - Note the bar jumps down to 70% to indiciate that most disk usage is here but not all.
* `target/debug/deps/dust-e78c9f87a17f24f3` - This is the largest file in this folder, but it is only 46M - Note the bar jumps down to 3% to indiciate the file is small.
* From here we can conclude:
* `target/debug/deps` takes 2/3 of the space in `target` and that `target/debug/deps` has a large number of relatively small files.
## Install ## Install
#### Cargo <a href="https://repology.org/project/du-dust/versions"><img src="https://repology.org/badge/vertical-allrepos/du-dust.svg" alt="Packaging status" align="right"></a> #### Cargo <a href="https://repology.org/project/du-dust/versions"><img src="https://repology.org/badge/vertical-allrepos/du-dust.svg" alt="Packaging status" align="right"></a>
@@ -79,8 +69,6 @@ Dust will list a slightly-less-than-the-terminal-height number of the biggest su
The different colors on the bars: These represent the combined tree hierarchy & disk usage. The shades of grey are used to indicate which parent folder a subfolder belongs to. For instance, look at the above screenshot. `.steam` is a folder taking 44% of the space. From the `.steam` bar is a light grey line that goes up. All these folders are inside `.steam` so if you delete `.steam` all that stuff will be gone too. The different colors on the bars: These represent the combined tree hierarchy & disk usage. The shades of grey are used to indicate which parent folder a subfolder belongs to. For instance, look at the above screenshot. `.steam` is a folder taking 44% of the space. From the `.steam` bar is a light grey line that goes up. All these folders are inside `.steam` so if you delete `.steam` all that stuff will be gone too.
If you are new to the tool I recommend to try tweaking the `-n` parameter. `dust -n 10`, `dust -n 50`.
## Usage ## Usage
``` ```
@@ -102,7 +90,7 @@ Usage: dust -B (--bars-on-right - Percent bars moved to right side of screen)
Usage: dust -i (Do not show hidden files) Usage: dust -i (Do not show hidden files)
Usage: dust -c (No colors [monochrome]) Usage: dust -c (No colors [monochrome])
Usage: dust -C (Force colors) Usage: dust -C (Force colors)
Usage: dust -f (Count files instead of diskspace [Counts by inode, to include duplicate inodes use dust -f -s]) Usage: dust -f (Count files instead of diskspace)
Usage: dust -t (Group by filetype) Usage: dust -t (Group by filetype)
Usage: dust -z 10M (min-size, Only include files larger than 10M) Usage: dust -z 10M (min-size, Only include files larger than 10M)
Usage: dust -e regex (Only include files matching this regex (eg dust -e "\.png$" would match png files)) Usage: dust -e regex (Only include files matching this regex (eg dust -e "\.png$" would match png files))
@@ -114,8 +102,7 @@ Usage: dust -S (Custom Stack size - Use if you see: 'fatal runtime error: stack
Usage: dust --skip-total (No total row will be displayed) Usage: dust --skip-total (No total row will be displayed)
Usage: dust -z 40000/30MB/20kib (Exclude output files/directories below size 40000 bytes / 30MB / 20KiB) Usage: dust -z 40000/30MB/20kib (Exclude output files/directories below size 40000 bytes / 30MB / 20KiB)
Usage: dust -j (Prints JSON representation of directories, try: dust -j | jq) Usage: dust -j (Prints JSON representation of directories, try: dust -j | jq)
Usage: dust --files0-from=FILE (Read NUL-terminated file paths from FILE; if FILE is '-', read from stdin) Usage: dust --files0-from=FILE (Reads null-terminated file paths from FILE); If FILE is - then read from stdin
Usage: dust --files-from=FILE (Read newline-terminated file paths from FILE; if FILE is '-', read from stdin)
Usage: dust --collapse=node-modules will keep the node-modules folder collapsed in display instead of recursively opening it Usage: dust --collapse=node-modules will keep the node-modules folder collapsed in display instead of recursively opening it
``` ```
@@ -135,16 +122,6 @@ reverse=true
- [dua](https://github.com/Byron/dua-cli/) - [dua](https://github.com/Byron/dua-cli/)
- [pdu](https://github.com/KSXGitHub/parallel-disk-usage) - [pdu](https://github.com/KSXGitHub/parallel-disk-usage)
- [dirstat-rs](https://github.com/scullionw/dirstat-rs) - [dirstat-rs](https://github.com/scullionw/dirstat-rs)
- `du -d 1 -h | sort -h` - du -d 1 -h | sort -h
## Why to use Dust over the Alternatives
Dust simply Does The Right Thing when handling lots of small files & directories. Dust keeps the output simple by only showing large entries.
Tools like ncdu & baobab, give you a view of directory sizes but you have no idea where the largest files are. For example directory A could have a size larger than directory B, but in fact the largest file is in B and not A. Finding this out via these other tools is not trivial whereas Dust will show the large file clearly in the tree hierarchy
Dust will not count hard links multiple times (unless you want to `-s`).
Typing `dust -n 90` will show you your 90 largest entries. `-n` is not quite like `head -n` or `tail -n`, dust is intelligent and chooses the largest entries
Note: Apparent-size is calculated slightly differently in dust to gdu. In dust each hard link is counted as using file_length space. In gdu only the first entry is counted.

View File

@@ -1,4 +1,3 @@
use clap::CommandFactory;
use clap_complete::{generate_to, shells::*}; use clap_complete::{generate_to, shells::*};
use clap_mangen::Man; use clap_mangen::Man;
use std::fs::File; use std::fs::File;
@@ -10,7 +9,7 @@ include!("src/cli.rs");
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
let outdir = "completions"; let outdir = "completions";
let app_name = "dust"; let app_name = "dust";
let mut cmd = Cli::command(); let mut cmd = build_cli();
generate_to(Bash, &mut cmd, app_name, outdir)?; generate_to(Bash, &mut cmd, app_name, outdir)?;
generate_to(Zsh, &mut cmd, app_name, outdir)?; generate_to(Zsh, &mut cmd, app_name, outdir)?;

View File

@@ -15,62 +15,39 @@ _dust() {
local context curcontext="$curcontext" state line local context curcontext="$curcontext" state line
_arguments "${_arguments_options[@]}" : \ _arguments "${_arguments_options[@]}" : \
'-d+[Depth to show]:DEPTH:_default' \ '-d+[Depth to show]:DEPTH: ' \
'--depth=[Depth to show]:DEPTH:_default' \ '--depth=[Depth to show]:DEPTH: ' \
'-T+[Number of threads to use]:THREADS:_default' \ '-T+[Number of threads to use]: : ' \
'--threads=[Number of threads to use]:THREADS:_default' \ '--threads=[Number of threads to use]: : ' \
'--config=[Specify a config file to use]:FILE:_files' \ '--config=[Specify a config file to use]:FILE:_files' \
'-n+[Display the '\''n'\'' largest entries. (Default is terminal_height)]:NUMBER:_default' \ '-n+[Number of lines of output to show. (Default is terminal_height - 10)]:NUMBER: ' \
'--number-of-lines=[Display the '\''n'\'' largest entries. (Default is terminal_height)]:NUMBER:_default' \ '--number-of-lines=[Number of lines of output to show. (Default is terminal_height - 10)]:NUMBER: ' \
'*-X+[Exclude any file or directory with this path]:PATH:_files' \ '*-X+[Exclude any file or directory with this path]:PATH:_files' \
'*--ignore-directory=[Exclude any file or directory with this path]:PATH:_files' \ '*--ignore-directory=[Exclude any file or directory with this path]:PATH:_files' \
'-I+[Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter]:FILE:_files' \ '-I+[Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter]:FILE:_files' \
'--ignore-all-in-file=[Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter]:FILE:_files' \ '--ignore-all-in-file=[Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter]:FILE:_files' \
'-z+[Minimum size file to include in output]:MIN_SIZE:_default' \ '-z+[Minimum size file to include in output]:MIN_SIZE: ' \
'--min-size=[Minimum size file to include in output]:MIN_SIZE:_default' \ '--min-size=[Minimum size file to include in output]:MIN_SIZE: ' \
'(-e --filter -t --file-types)*-v+[Exclude filepaths matching this regex. To ignore png files type\: -v "\\.png\$"]:REGEX:_default' \ '(-e --filter -t --file_types)*-v+[Exclude filepaths matching this regex. To ignore png files type\: -v "\\.png\$" ]:REGEX: ' \
'(-e --filter -t --file-types)*--invert-filter=[Exclude filepaths matching this regex. To ignore png files type\: -v "\\.png\$"]:REGEX:_default' \ '(-e --filter -t --file_types)*--invert-filter=[Exclude filepaths matching this regex. To ignore png files type\: -v "\\.png\$" ]:REGEX: ' \
'(-t --file-types)*-e+[Only include filepaths matching this regex. For png files type\: -e "\\.png\$"]:REGEX:_default' \ '(-t --file_types)*-e+[Only include filepaths matching this regex. For png files type\: -e "\\.png\$" ]:REGEX: ' \
'(-t --file-types)*--filter=[Only include filepaths matching this regex. For png files type\: -e "\\.png\$"]:REGEX:_default' \ '(-t --file_types)*--filter=[Only include filepaths matching this regex. For png files type\: -e "\\.png\$" ]:REGEX: ' \
'-w+[Specify width of output overriding the auto detection of terminal width]:WIDTH:_default' \ '-w+[Specify width of output overriding the auto detection of terminal width]:WIDTH: ' \
'--terminal-width=[Specify width of output overriding the auto detection of terminal width]:WIDTH:_default' \ '--terminal_width=[Specify width of output overriding the auto detection of terminal width]:WIDTH: ' \
'-o+[Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size]:FORMAT:((si\:"SI prefix (powers of 1000)" '-o+[Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.]:FORMAT:(si b k m g t kb mb gb tb)' \
b\:"byte (B)" '--output-format=[Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.]:FORMAT:(si b k m g t kb mb gb tb)' \
k\:"kibibyte (KiB)" '-S+[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]:STACK_SIZE: ' \
m\:"mebibyte (MiB)" '--stack-size=[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]:STACK_SIZE: ' \
g\:"gibibyte (GiB)" '-M+[+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => \[curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)]: : ' \
t\:"tebibyte (TiB)" '--mtime=[+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => \[curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)]: : ' \
kb\:"kilobyte (kB)" '-A+[just like -mtime, but based on file access time]: : ' \
mb\:"megabyte (MB)" '--atime=[just like -mtime, but based on file access time]: : ' \
gb\:"gigabyte (GB)" '-y+[just like -mtime, but based on file change time]: : ' \
tb\:"terabyte (TB)"))' \ '--ctime=[just like -mtime, but based on file change time]: : ' \
'--output-format=[Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size]:FORMAT:((si\:"SI prefix (powers of 1000)" '--files0-from=[run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input]: :_files' \
b\:"byte (B)" '*--collapse=[Keep these directories collapsed]: :_files' \
k\:"kibibyte (KiB)" '-m+[Directory '\''size'\'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time]: :(a c m)' \
m\:"mebibyte (MiB)" '--filetime=[Directory '\''size'\'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time]: :(a c m)' \
g\:"gibibyte (GiB)"
t\:"tebibyte (TiB)"
kb\:"kilobyte (kB)"
mb\:"megabyte (MB)"
gb\:"gigabyte (GB)"
tb\:"terabyte (TB)"))' \
'-S+[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]:STACK_SIZE:_default' \
'--stack-size=[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]:STACK_SIZE:_default' \
'-M+[+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => \[curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)]:MTIME:_default' \
'--mtime=[+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => \[curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)]:MTIME:_default' \
'-A+[just like -mtime, but based on file access time]:ATIME:_default' \
'--atime=[just like -mtime, but based on file access time]:ATIME:_default' \
'-y+[just like -mtime, but based on file change time]:CTIME:_default' \
'--ctime=[just like -mtime, but based on file change time]:CTIME:_default' \
'(--files-from)--files0-from=[Read NUL-terminated paths from FILE (use \`-\` for stdin)]:FILES0_FROM:_files' \
'(--files0-from)--files-from=[Read newline-terminated paths from FILE (use \`-\` for stdin)]:FILES_FROM:_files' \
'*--collapse=[Keep these directories collapsed]:COLLAPSE:_files' \
'-m+[Directory '\''size'\'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time]:FILETIME:((a\:"last accessed time"
c\:"last changed time"
m\:"last modified time"))' \
'--filetime=[Directory '\''size'\'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time]:FILETIME:((a\:"last accessed time"
c\:"last changed time"
m\:"last modified time"))' \
'-p[Subdirectories will not have their path shortened]' \ '-p[Subdirectories will not have their path shortened]' \
'--full-paths[Subdirectories will not have their path shortened]' \ '--full-paths[Subdirectories will not have their path shortened]' \
'-L[dereference sym links - Treat sym links as directories and go into them]' \ '-L[dereference sym links - Treat sym links as directories and go into them]' \
@@ -95,23 +72,23 @@ m\:"last modified time"))' \
'-f[Directory '\''size'\'' is number of child files instead of disk size]' \ '-f[Directory '\''size'\'' is number of child files instead of disk size]' \
'--filecount[Directory '\''size'\'' is number of child files instead of disk size]' \ '--filecount[Directory '\''size'\'' is number of child files instead of disk size]' \
'-i[Do not display hidden files]' \ '-i[Do not display hidden files]' \
'--ignore-hidden[Do not display hidden files]' \ '--ignore_hidden[Do not display hidden files]' \
'(-d --depth -D --only-dir)-t[show only these file types]' \ '(-d --depth -D --only-dir)-t[show only these file types]' \
'(-d --depth -D --only-dir)--file-types[show only these file types]' \ '(-d --depth -D --only-dir)--file_types[show only these file types]' \
'-P[Disable the progress indication]' \ '-P[Disable the progress indication.]' \
'--no-progress[Disable the progress indication]' \ '--no-progress[Disable the progress indication.]' \
'--print-errors[Print path with errors]' \ '--print-errors[Print path with errors.]' \
'(-F --only-file -t --file-types)-D[Only directories will be displayed]' \ '(-F --only-file -t --file_types)-D[Only directories will be displayed.]' \
'(-F --only-file -t --file-types)--only-dir[Only directories will be displayed]' \ '(-F --only-file -t --file_types)--only-dir[Only directories will be displayed.]' \
'(-D --only-dir)-F[Only files will be displayed. (Finds your largest files)]' \ '(-D --only-dir)-F[Only files will be displayed. (Finds your largest files)]' \
'(-D --only-dir)--only-file[Only files will be displayed. (Finds your largest files)]' \ '(-D --only-dir)--only-file[Only files will be displayed. (Finds your largest files)]' \
'-j[Output the directory tree as json to the current directory]' \ '-j[Output the directory tree as json to the current directory]' \
'--output-json[Output the directory tree as json to the current directory]' \ '--output-json[Output the directory tree as json to the current directory]' \
'-h[Print help (see more with '\''--help'\'')]' \ '-h[Print help]' \
'--help[Print help (see more with '\''--help'\'')]' \ '--help[Print help]' \
'-V[Print version]' \ '-V[Print version]' \
'--version[Print version]' \ '--version[Print version]' \
'*::params -- Input files or directories:_files' \ '*::params:_files' \
&& ret=0 && ret=0
} }

View File

@@ -21,80 +21,79 @@ Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
$completions = @(switch ($command) { $completions = @(switch ($command) {
'dust' { 'dust' {
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'Depth to show') [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Depth to show')
[CompletionResult]::new('--depth', '--depth', [CompletionResultType]::ParameterName, 'Depth to show') [CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Depth to show')
[CompletionResult]::new('-T', '-T ', [CompletionResultType]::ParameterName, 'Number of threads to use') [CompletionResult]::new('-T', 'T ', [CompletionResultType]::ParameterName, 'Number of threads to use')
[CompletionResult]::new('--threads', '--threads', [CompletionResultType]::ParameterName, 'Number of threads to use') [CompletionResult]::new('--threads', 'threads', [CompletionResultType]::ParameterName, 'Number of threads to use')
[CompletionResult]::new('--config', '--config', [CompletionResultType]::ParameterName, 'Specify a config file to use') [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Specify a config file to use')
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'Display the ''n'' largest entries. (Default is terminal_height)') [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)')
[CompletionResult]::new('--number-of-lines', '--number-of-lines', [CompletionResultType]::ParameterName, 'Display the ''n'' largest entries. (Default is terminal_height)') [CompletionResult]::new('--number-of-lines', 'number-of-lines', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)')
[CompletionResult]::new('-X', '-X ', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this path') [CompletionResult]::new('-X', 'X ', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this path')
[CompletionResult]::new('--ignore-directory', '--ignore-directory', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this path') [CompletionResult]::new('--ignore-directory', 'ignore-directory', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this path')
[CompletionResult]::new('-I', '-I ', [CompletionResultType]::ParameterName, 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter') [CompletionResult]::new('-I', 'I ', [CompletionResultType]::ParameterName, 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter')
[CompletionResult]::new('--ignore-all-in-file', '--ignore-all-in-file', [CompletionResultType]::ParameterName, 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter') [CompletionResult]::new('--ignore-all-in-file', 'ignore-all-in-file', [CompletionResultType]::ParameterName, 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter')
[CompletionResult]::new('-z', '-z', [CompletionResultType]::ParameterName, 'Minimum size file to include in output') [CompletionResult]::new('-z', 'z', [CompletionResultType]::ParameterName, 'Minimum size file to include in output')
[CompletionResult]::new('--min-size', '--min-size', [CompletionResultType]::ParameterName, 'Minimum size file to include in output') [CompletionResult]::new('--min-size', 'min-size', [CompletionResultType]::ParameterName, 'Minimum size file to include in output')
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$"') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
[CompletionResult]::new('--invert-filter', '--invert-filter', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$"') [CompletionResult]::new('--invert-filter', 'invert-filter', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
[CompletionResult]::new('-e', '-e', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$"') [CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$" ')
[CompletionResult]::new('--filter', '--filter', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$"') [CompletionResult]::new('--filter', 'filter', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$" ')
[CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width') [CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
[CompletionResult]::new('--terminal-width', '--terminal-width', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width') [CompletionResult]::new('--terminal_width', 'terminal_width', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size') [CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.')
[CompletionResult]::new('--output-format', '--output-format', [CompletionResultType]::ParameterName, 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size') [CompletionResult]::new('--output-format', 'output-format', [CompletionResultType]::ParameterName, 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.')
[CompletionResult]::new('-S', '-S ', [CompletionResultType]::ParameterName, 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)') [CompletionResult]::new('-S', 'S ', [CompletionResultType]::ParameterName, 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)')
[CompletionResult]::new('--stack-size', '--stack-size', [CompletionResultType]::ParameterName, 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)') [CompletionResult]::new('--stack-size', 'stack-size', [CompletionResultType]::ParameterName, 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)')
[CompletionResult]::new('-M', '-M ', [CompletionResultType]::ParameterName, '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)') [CompletionResult]::new('-M', 'M ', [CompletionResultType]::ParameterName, '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)')
[CompletionResult]::new('--mtime', '--mtime', [CompletionResultType]::ParameterName, '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)') [CompletionResult]::new('--mtime', 'mtime', [CompletionResultType]::ParameterName, '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)')
[CompletionResult]::new('-A', '-A ', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file access time') [CompletionResult]::new('-A', 'A ', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file access time')
[CompletionResult]::new('--atime', '--atime', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file access time') [CompletionResult]::new('--atime', 'atime', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file access time')
[CompletionResult]::new('-y', '-y', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file change time') [CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file change time')
[CompletionResult]::new('--ctime', '--ctime', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file change time') [CompletionResult]::new('--ctime', 'ctime', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file change time')
[CompletionResult]::new('--files0-from', '--files0-from', [CompletionResultType]::ParameterName, 'Read NUL-terminated paths from FILE (use `-` for stdin)') [CompletionResult]::new('--files0-from', 'files0-from', [CompletionResultType]::ParameterName, 'run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input')
[CompletionResult]::new('--files-from', '--files-from', [CompletionResultType]::ParameterName, 'Read newline-terminated paths from FILE (use `-` for stdin)') [CompletionResult]::new('--collapse', 'collapse', [CompletionResultType]::ParameterName, 'Keep these directories collapsed')
[CompletionResult]::new('--collapse', '--collapse', [CompletionResultType]::ParameterName, 'Keep these directories collapsed') [CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time')
[CompletionResult]::new('-m', '-m', [CompletionResultType]::ParameterName, 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time') [CompletionResult]::new('--filetime', 'filetime', [CompletionResultType]::ParameterName, 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time')
[CompletionResult]::new('--filetime', '--filetime', [CompletionResultType]::ParameterName, 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time') [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened')
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened') [CompletionResult]::new('--full-paths', 'full-paths', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened')
[CompletionResult]::new('--full-paths', '--full-paths', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened') [CompletionResult]::new('-L', 'L ', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them')
[CompletionResult]::new('-L', '-L ', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them') [CompletionResult]::new('--dereference-links', 'dereference-links', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them')
[CompletionResult]::new('--dereference-links', '--dereference-links', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them') [CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory')
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory') [CompletionResult]::new('--limit-filesystem', 'limit-filesystem', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory')
[CompletionResult]::new('--limit-filesystem', '--limit-filesystem', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory') [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Use file length instead of blocks')
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Use file length instead of blocks') [CompletionResult]::new('--apparent-size', 'apparent-size', [CompletionResultType]::ParameterName, 'Use file length instead of blocks')
[CompletionResult]::new('--apparent-size', '--apparent-size', [CompletionResultType]::ParameterName, 'Use file length instead of blocks') [CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)')
[CompletionResult]::new('-r', '-r', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)') [CompletionResult]::new('--reverse', 'reverse', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)')
[CompletionResult]::new('--reverse', '--reverse', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)') [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)') [CompletionResult]::new('--no-colors', 'no-colors', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
[CompletionResult]::new('--no-colors', '--no-colors', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)') [CompletionResult]::new('-C', 'C ', [CompletionResultType]::ParameterName, 'Force colors print')
[CompletionResult]::new('-C', '-C ', [CompletionResultType]::ParameterName, 'Force colors print') [CompletionResult]::new('--force-colors', 'force-colors', [CompletionResultType]::ParameterName, 'Force colors print')
[CompletionResult]::new('--force-colors', '--force-colors', [CompletionResultType]::ParameterName, 'Force colors print') [CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed')
[CompletionResult]::new('-b', '-b', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed') [CompletionResult]::new('--no-percent-bars', 'no-percent-bars', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed')
[CompletionResult]::new('--no-percent-bars', '--no-percent-bars', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed') [CompletionResult]::new('-B', 'B ', [CompletionResultType]::ParameterName, 'percent bars moved to right side of screen')
[CompletionResult]::new('-B', '-B ', [CompletionResultType]::ParameterName, 'percent bars moved to right side of screen') [CompletionResult]::new('--bars-on-right', 'bars-on-right', [CompletionResultType]::ParameterName, 'percent bars moved to right side of screen')
[CompletionResult]::new('--bars-on-right', '--bars-on-right', [CompletionResultType]::ParameterName, 'percent bars moved to right side of screen') [CompletionResult]::new('-R', 'R ', [CompletionResultType]::ParameterName, 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)')
[CompletionResult]::new('-R', '-R ', [CompletionResultType]::ParameterName, 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)') [CompletionResult]::new('--screen-reader', 'screen-reader', [CompletionResultType]::ParameterName, 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)')
[CompletionResult]::new('--screen-reader', '--screen-reader', [CompletionResultType]::ParameterName, 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)') [CompletionResult]::new('--skip-total', 'skip-total', [CompletionResultType]::ParameterName, 'No total row will be displayed')
[CompletionResult]::new('--skip-total', '--skip-total', [CompletionResultType]::ParameterName, 'No total row will be displayed') [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files instead of disk size')
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files instead of disk size') [CompletionResult]::new('--filecount', 'filecount', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files instead of disk size')
[CompletionResult]::new('--filecount', '--filecount', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files instead of disk size') [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Do not display hidden files')
[CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'Do not display hidden files') [CompletionResult]::new('--ignore_hidden', 'ignore_hidden', [CompletionResultType]::ParameterName, 'Do not display hidden files')
[CompletionResult]::new('--ignore-hidden', '--ignore-hidden', [CompletionResultType]::ParameterName, 'Do not display hidden files') [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'show only these file types')
[CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'show only these file types') [CompletionResult]::new('--file_types', 'file_types', [CompletionResultType]::ParameterName, 'show only these file types')
[CompletionResult]::new('--file-types', '--file-types', [CompletionResultType]::ParameterName, 'show only these file types') [CompletionResult]::new('-P', 'P ', [CompletionResultType]::ParameterName, 'Disable the progress indication.')
[CompletionResult]::new('-P', '-P ', [CompletionResultType]::ParameterName, 'Disable the progress indication') [CompletionResult]::new('--no-progress', 'no-progress', [CompletionResultType]::ParameterName, 'Disable the progress indication.')
[CompletionResult]::new('--no-progress', '--no-progress', [CompletionResultType]::ParameterName, 'Disable the progress indication') [CompletionResult]::new('--print-errors', 'print-errors', [CompletionResultType]::ParameterName, 'Print path with errors.')
[CompletionResult]::new('--print-errors', '--print-errors', [CompletionResultType]::ParameterName, 'Print path with errors') [CompletionResult]::new('-D', 'D ', [CompletionResultType]::ParameterName, 'Only directories will be displayed.')
[CompletionResult]::new('-D', '-D ', [CompletionResultType]::ParameterName, 'Only directories will be displayed') [CompletionResult]::new('--only-dir', 'only-dir', [CompletionResultType]::ParameterName, 'Only directories will be displayed.')
[CompletionResult]::new('--only-dir', '--only-dir', [CompletionResultType]::ParameterName, 'Only directories will be displayed') [CompletionResult]::new('-F', 'F ', [CompletionResultType]::ParameterName, 'Only files will be displayed. (Finds your largest files)')
[CompletionResult]::new('-F', '-F ', [CompletionResultType]::ParameterName, 'Only files will be displayed. (Finds your largest files)') [CompletionResult]::new('--only-file', 'only-file', [CompletionResultType]::ParameterName, 'Only files will be displayed. (Finds your largest files)')
[CompletionResult]::new('--only-file', '--only-file', [CompletionResultType]::ParameterName, 'Only files will be displayed. (Finds your largest files)') [CompletionResult]::new('-j', 'j', [CompletionResultType]::ParameterName, 'Output the directory tree as json to the current directory')
[CompletionResult]::new('-j', '-j', [CompletionResultType]::ParameterName, 'Output the directory tree as json to the current directory') [CompletionResult]::new('--output-json', 'output-json', [CompletionResultType]::ParameterName, 'Output the directory tree as json to the current directory')
[CompletionResult]::new('--output-json', '--output-json', [CompletionResultType]::ParameterName, 'Output the directory tree as json to the current directory') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version') [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break break
} }
}) })

View File

@@ -1,16 +1,12 @@
_dust() { _dust() {
local i cur prev opts cmd local i cur prev opts cmd
COMPREPLY=() COMPREPLY=()
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then cur="${COMP_WORDS[COMP_CWORD]}"
cur="$2" prev="${COMP_WORDS[COMP_CWORD-1]}"
else
cur="${COMP_WORDS[COMP_CWORD]}"
fi
prev="$3"
cmd="" cmd=""
opts="" opts=""
for i in "${COMP_WORDS[@]:0:COMP_CWORD}" for i in ${COMP_WORDS[@]}
do do
case "${cmd},${i}" in case "${cmd},${i}" in
",$1") ",$1")
@@ -23,7 +19,7 @@ _dust() {
case "${cmd}" in case "${cmd}" in
dust) dust)
opts="-d -T -n -p -X -I -L -x -s -r -c -C -b -B -z -R -f -i -v -e -t -w -P -D -F -o -S -j -M -A -y -m -h -V --depth --threads --config --number-of-lines --full-paths --ignore-directory --ignore-all-in-file --dereference-links --limit-filesystem --apparent-size --reverse --no-colors --force-colors --no-percent-bars --bars-on-right --min-size --screen-reader --skip-total --filecount --ignore-hidden --invert-filter --filter --file-types --terminal-width --no-progress --print-errors --only-dir --only-file --output-format --stack-size --output-json --mtime --atime --ctime --files0-from --files-from --collapse --filetime --help --version [PATH]..." opts="-d -T -n -p -X -I -L -x -s -r -c -C -b -B -z -R -f -i -v -e -t -w -P -D -F -o -S -j -M -A -y -m -h -V --depth --threads --config --number-of-lines --full-paths --ignore-directory --ignore-all-in-file --dereference-links --limit-filesystem --apparent-size --reverse --no-colors --force-colors --no-percent-bars --bars-on-right --min-size --screen-reader --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --no-progress --print-errors --only-dir --only-file --output-format --stack-size --output-json --mtime --atime --ctime --files0-from --collapse --filetime --help --version [PATH]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@@ -130,7 +126,7 @@ _dust() {
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
;; ;;
--terminal-width) --terminal_width)
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
;; ;;
@@ -182,10 +178,6 @@ _dust() {
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
;; ;;
--files-from)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--collapse) --collapse)
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0

View File

@@ -23,22 +23,22 @@ set edit:completion:arg-completer[dust] = {|@words|
cand -T 'Number of threads to use' cand -T 'Number of threads to use'
cand --threads 'Number of threads to use' cand --threads 'Number of threads to use'
cand --config 'Specify a config file to use' cand --config 'Specify a config file to use'
cand -n 'Display the ''n'' largest entries. (Default is terminal_height)' cand -n 'Number of lines of output to show. (Default is terminal_height - 10)'
cand --number-of-lines 'Display the ''n'' largest entries. (Default is terminal_height)' cand --number-of-lines 'Number of lines of output to show. (Default is terminal_height - 10)'
cand -X 'Exclude any file or directory with this path' cand -X 'Exclude any file or directory with this path'
cand --ignore-directory 'Exclude any file or directory with this path' cand --ignore-directory 'Exclude any file or directory with this path'
cand -I 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter' cand -I 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter'
cand --ignore-all-in-file 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter' cand --ignore-all-in-file 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter'
cand -z 'Minimum size file to include in output' cand -z 'Minimum size file to include in output'
cand --min-size 'Minimum size file to include in output' cand --min-size 'Minimum size file to include in output'
cand -v 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$"' cand -v 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" '
cand --invert-filter 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$"' cand --invert-filter 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" '
cand -e 'Only include filepaths matching this regex. For png files type: -e "\.png$"' cand -e 'Only include filepaths matching this regex. For png files type: -e "\.png$" '
cand --filter 'Only include filepaths matching this regex. For png files type: -e "\.png$"' cand --filter 'Only include filepaths matching this regex. For png files type: -e "\.png$" '
cand -w 'Specify width of output overriding the auto detection of terminal width' cand -w 'Specify width of output overriding the auto detection of terminal width'
cand --terminal-width 'Specify width of output overriding the auto detection of terminal width' cand --terminal_width 'Specify width of output overriding the auto detection of terminal width'
cand -o 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size' cand -o 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.'
cand --output-format 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size' cand --output-format 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.'
cand -S 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)' cand -S 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)'
cand --stack-size 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)' cand --stack-size 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)'
cand -M '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)' cand -M '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)'
@@ -47,8 +47,7 @@ set edit:completion:arg-completer[dust] = {|@words|
cand --atime 'just like -mtime, but based on file access time' cand --atime 'just like -mtime, but based on file access time'
cand -y 'just like -mtime, but based on file change time' cand -y 'just like -mtime, but based on file change time'
cand --ctime 'just like -mtime, but based on file change time' cand --ctime 'just like -mtime, but based on file change time'
cand --files0-from 'Read NUL-terminated paths from FILE (use `-` for stdin)' cand --files0-from 'run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input'
cand --files-from 'Read newline-terminated paths from FILE (use `-` for stdin)'
cand --collapse 'Keep these directories collapsed' cand --collapse 'Keep these directories collapsed'
cand -m 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time' cand -m 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time'
cand --filetime 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time' cand --filetime 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time'
@@ -76,20 +75,20 @@ set edit:completion:arg-completer[dust] = {|@words|
cand -f 'Directory ''size'' is number of child files instead of disk size' cand -f 'Directory ''size'' is number of child files instead of disk size'
cand --filecount 'Directory ''size'' is number of child files instead of disk size' cand --filecount 'Directory ''size'' is number of child files instead of disk size'
cand -i 'Do not display hidden files' cand -i 'Do not display hidden files'
cand --ignore-hidden 'Do not display hidden files' cand --ignore_hidden 'Do not display hidden files'
cand -t 'show only these file types' cand -t 'show only these file types'
cand --file-types 'show only these file types' cand --file_types 'show only these file types'
cand -P 'Disable the progress indication' cand -P 'Disable the progress indication.'
cand --no-progress 'Disable the progress indication' cand --no-progress 'Disable the progress indication.'
cand --print-errors 'Print path with errors' cand --print-errors 'Print path with errors.'
cand -D 'Only directories will be displayed' cand -D 'Only directories will be displayed.'
cand --only-dir 'Only directories will be displayed' cand --only-dir 'Only directories will be displayed.'
cand -F 'Only files will be displayed. (Finds your largest files)' cand -F 'Only files will be displayed. (Finds your largest files)'
cand --only-file 'Only files will be displayed. (Finds your largest files)' cand --only-file 'Only files will be displayed. (Finds your largest files)'
cand -j 'Output the directory tree as json to the current directory' cand -j 'Output the directory tree as json to the current directory'
cand --output-json 'Output the directory tree as json to the current directory' cand --output-json 'Output the directory tree as json to the current directory'
cand -h 'Print help (see more with ''--help'')' cand -h 'Print help'
cand --help 'Print help (see more with ''--help'')' cand --help 'Print help'
cand -V 'Print version' cand -V 'Print version'
cand --version 'Print version' cand --version 'Print version'
} }

View File

@@ -1,33 +1,21 @@
complete -c dust -s d -l depth -d 'Depth to show' -r complete -c dust -s d -l depth -d 'Depth to show' -r
complete -c dust -s T -l threads -d 'Number of threads to use' -r complete -c dust -s T -l threads -d 'Number of threads to use' -r
complete -c dust -l config -d 'Specify a config file to use' -r -F complete -c dust -l config -d 'Specify a config file to use' -r -F
complete -c dust -s n -l number-of-lines -d 'Display the \'n\' largest entries. (Default is terminal_height)' -r complete -c dust -s n -l number-of-lines -d 'Number of lines of output to show. (Default is terminal_height - 10)' -r
complete -c dust -s X -l ignore-directory -d 'Exclude any file or directory with this path' -r -F complete -c dust -s X -l ignore-directory -d 'Exclude any file or directory with this path' -r -F
complete -c dust -s I -l ignore-all-in-file -d 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter' -r -F complete -c dust -s I -l ignore-all-in-file -d 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter' -r -F
complete -c dust -s z -l min-size -d 'Minimum size file to include in output' -r complete -c dust -s z -l min-size -d 'Minimum size file to include in output' -r
complete -c dust -s v -l invert-filter -d 'Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$"' -r complete -c dust -s v -l invert-filter -d 'Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ' -r
complete -c dust -s e -l filter -d 'Only include filepaths matching this regex. For png files type: -e "\\.png$"' -r complete -c dust -s e -l filter -d 'Only include filepaths matching this regex. For png files type: -e "\\.png$" ' -r
complete -c dust -s w -l terminal-width -d 'Specify width of output overriding the auto detection of terminal width' -r complete -c dust -s w -l terminal_width -d 'Specify width of output overriding the auto detection of terminal width' -r
complete -c dust -s o -l output-format -d 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size' -r -f -a "si\t'SI prefix (powers of 1000)' complete -c dust -s o -l output-format -d 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.' -r -f -a "{si\t'',b\t'',k\t'',m\t'',g\t'',t\t'',kb\t'',mb\t'',gb\t'',tb\t''}"
b\t'byte (B)'
k\t'kibibyte (KiB)'
m\t'mebibyte (MiB)'
g\t'gibibyte (GiB)'
t\t'tebibyte (TiB)'
kb\t'kilobyte (kB)'
mb\t'megabyte (MB)'
gb\t'gigabyte (GB)'
tb\t'terabyte (TB)'"
complete -c dust -s S -l stack-size -d 'Specify memory to use as stack size - use if you see: \'fatal runtime error: stack overflow\' (default low memory=1048576, high memory=1073741824)' -r complete -c dust -s S -l stack-size -d 'Specify memory to use as stack size - use if you see: \'fatal runtime error: stack overflow\' (default low memory=1048576, high memory=1073741824)' -r
complete -c dust -s M -l mtime -d '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)' -r complete -c dust -s M -l mtime -d '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)' -r
complete -c dust -s A -l atime -d 'just like -mtime, but based on file access time' -r complete -c dust -s A -l atime -d 'just like -mtime, but based on file access time' -r
complete -c dust -s y -l ctime -d 'just like -mtime, but based on file change time' -r complete -c dust -s y -l ctime -d 'just like -mtime, but based on file change time' -r
complete -c dust -l files0-from -d 'Read NUL-terminated paths from FILE (use `-` for stdin)' -r -F complete -c dust -l files0-from -d 'run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input' -r -F
complete -c dust -l files-from -d 'Read newline-terminated paths from FILE (use `-` for stdin)' -r -F
complete -c dust -l collapse -d 'Keep these directories collapsed' -r -F complete -c dust -l collapse -d 'Keep these directories collapsed' -r -F
complete -c dust -s m -l filetime -d 'Directory \'size\' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time' -r -f -a "a\t'last accessed time' complete -c dust -s m -l filetime -d 'Directory \'size\' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time' -r -f -a "{a\t'',c\t'',m\t''}"
c\t'last changed time'
m\t'last modified time'"
complete -c dust -s p -l full-paths -d 'Subdirectories will not have their path shortened' complete -c dust -s p -l full-paths -d 'Subdirectories will not have their path shortened'
complete -c dust -s L -l dereference-links -d 'dereference sym links - Treat sym links as directories and go into them' complete -c dust -s L -l dereference-links -d 'dereference sym links - Treat sym links as directories and go into them'
complete -c dust -s x -l limit-filesystem -d 'Only count the files and directories on the same filesystem as the supplied directory' complete -c dust -s x -l limit-filesystem -d 'Only count the files and directories on the same filesystem as the supplied directory'
@@ -40,12 +28,12 @@ complete -c dust -s B -l bars-on-right -d 'percent bars moved to right side of s
complete -c dust -s R -l screen-reader -d 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)' complete -c dust -s R -l screen-reader -d 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)'
complete -c dust -l skip-total -d 'No total row will be displayed' complete -c dust -l skip-total -d 'No total row will be displayed'
complete -c dust -s f -l filecount -d 'Directory \'size\' is number of child files instead of disk size' complete -c dust -s f -l filecount -d 'Directory \'size\' is number of child files instead of disk size'
complete -c dust -s i -l ignore-hidden -d 'Do not display hidden files' 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 t -l file_types -d 'show only these file types'
complete -c dust -s P -l no-progress -d 'Disable the progress indication' complete -c dust -s P -l no-progress -d 'Disable the progress indication.'
complete -c dust -l print-errors -d 'Print path with errors' complete -c dust -l print-errors -d 'Print path with errors.'
complete -c dust -s D -l only-dir -d 'Only directories will be displayed' complete -c dust -s D -l only-dir -d 'Only directories will be displayed.'
complete -c dust -s F -l only-file -d 'Only files will be displayed. (Finds your largest files)' complete -c dust -s F -l only-file -d 'Only files will be displayed. (Finds your largest files)'
complete -c dust -s j -l output-json -d 'Output the directory tree as json to the current directory' complete -c dust -s j -l output-json -d 'Output the directory tree as json to the current directory'
complete -c dust -s h -l help -d 'Print help (see more with \'--help\')' complete -c dust -s h -l help -d 'Print help'
complete -c dust -s V -l version -d 'Print version' complete -c dust -s V -l version -d 'Print version'

View File

@@ -26,5 +26,3 @@ ignore-hidden=true
# print sizes in powers of 1000 (e.g., 1.1G) # print sizes in powers of 1000 (e.g., 1.1G)
output-format="si" output-format="si"
number-of-lines=5

View File

@@ -1,33 +1,33 @@
.ie \n(.g .ds Aq \(aq .ie \n(.g .ds Aq \(aq
.el .ds Aq ' .el .ds Aq '
.TH Dust 1 "Dust 1.2.3" .TH Dust 1 "Dust 1.1.1"
.SH NAME .SH NAME
Dust \- Like du but more intuitive Dust \- Like du but more intuitive
.SH SYNOPSIS .SH SYNOPSIS
\fBdust\fR [\fB\-d\fR|\fB\-\-depth\fR] [\fB\-T\fR|\fB\-\-threads\fR] [\fB\-\-config\fR] [\fB\-n\fR|\fB\-\-number\-of\-lines\fR] [\fB\-p\fR|\fB\-\-full\-paths\fR] [\fB\-X\fR|\fB\-\-ignore\-directory\fR] [\fB\-I\fR|\fB\-\-ignore\-all\-in\-file\fR] [\fB\-L\fR|\fB\-\-dereference\-links\fR] [\fB\-x\fR|\fB\-\-limit\-filesystem\fR] [\fB\-s\fR|\fB\-\-apparent\-size\fR] [\fB\-r\fR|\fB\-\-reverse\fR] [\fB\-c\fR|\fB\-\-no\-colors\fR] [\fB\-C\fR|\fB\-\-force\-colors\fR] [\fB\-b\fR|\fB\-\-no\-percent\-bars\fR] [\fB\-B\fR|\fB\-\-bars\-on\-right\fR] [\fB\-z\fR|\fB\-\-min\-size\fR] [\fB\-R\fR|\fB\-\-screen\-reader\fR] [\fB\-\-skip\-total\fR] [\fB\-f\fR|\fB\-\-filecount\fR] [\fB\-i\fR|\fB\-\-ignore\-hidden\fR] [\fB\-v\fR|\fB\-\-invert\-filter\fR] [\fB\-e\fR|\fB\-\-filter\fR] [\fB\-t\fR|\fB\-\-file\-types\fR] [\fB\-w\fR|\fB\-\-terminal\-width\fR] [\fB\-P\fR|\fB\-\-no\-progress\fR] [\fB\-\-print\-errors\fR] [\fB\-D\fR|\fB\-\-only\-dir\fR] [\fB\-F\fR|\fB\-\-only\-file\fR] [\fB\-o\fR|\fB\-\-output\-format\fR] [\fB\-S\fR|\fB\-\-stack\-size\fR] [\fB\-j\fR|\fB\-\-output\-json\fR] [\fB\-M\fR|\fB\-\-mtime\fR] [\fB\-A\fR|\fB\-\-atime\fR] [\fB\-y\fR|\fB\-\-ctime\fR] [\fB\-\-files0\-from\fR] [\fB\-\-files\-from\fR] [\fB\-\-collapse\fR] [\fB\-m\fR|\fB\-\-filetime\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIPATH\fR] \fBdust\fR [\fB\-d\fR|\fB\-\-depth\fR] [\fB\-T\fR|\fB\-\-threads\fR] [\fB\-\-config\fR] [\fB\-n\fR|\fB\-\-number\-of\-lines\fR] [\fB\-p\fR|\fB\-\-full\-paths\fR] [\fB\-X\fR|\fB\-\-ignore\-directory\fR] [\fB\-I\fR|\fB\-\-ignore\-all\-in\-file\fR] [\fB\-L\fR|\fB\-\-dereference\-links\fR] [\fB\-x\fR|\fB\-\-limit\-filesystem\fR] [\fB\-s\fR|\fB\-\-apparent\-size\fR] [\fB\-r\fR|\fB\-\-reverse\fR] [\fB\-c\fR|\fB\-\-no\-colors\fR] [\fB\-C\fR|\fB\-\-force\-colors\fR] [\fB\-b\fR|\fB\-\-no\-percent\-bars\fR] [\fB\-B\fR|\fB\-\-bars\-on\-right\fR] [\fB\-z\fR|\fB\-\-min\-size\fR] [\fB\-R\fR|\fB\-\-screen\-reader\fR] [\fB\-\-skip\-total\fR] [\fB\-f\fR|\fB\-\-filecount\fR] [\fB\-i\fR|\fB\-\-ignore_hidden\fR] [\fB\-v\fR|\fB\-\-invert\-filter\fR] [\fB\-e\fR|\fB\-\-filter\fR] [\fB\-t\fR|\fB\-\-file_types\fR] [\fB\-w\fR|\fB\-\-terminal_width\fR] [\fB\-P\fR|\fB\-\-no\-progress\fR] [\fB\-\-print\-errors\fR] [\fB\-D\fR|\fB\-\-only\-dir\fR] [\fB\-F\fR|\fB\-\-only\-file\fR] [\fB\-o\fR|\fB\-\-output\-format\fR] [\fB\-S\fR|\fB\-\-stack\-size\fR] [\fB\-j\fR|\fB\-\-output\-json\fR] [\fB\-M\fR|\fB\-\-mtime\fR] [\fB\-A\fR|\fB\-\-atime\fR] [\fB\-y\fR|\fB\-\-ctime\fR] [\fB\-\-files0\-from\fR] [\fB\-\-collapse\fR] [\fB\-m\fR|\fB\-\-filetime\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIPATH\fR]
.SH DESCRIPTION .SH DESCRIPTION
Like du but more intuitive Like du but more intuitive
.SH OPTIONS .SH OPTIONS
.TP .TP
\fB\-d\fR, \fB\-\-depth\fR \fI<DEPTH>\fR \fB\-d\fR, \fB\-\-depth\fR=\fIDEPTH\fR
Depth to show Depth to show
.TP .TP
\fB\-T\fR, \fB\-\-threads\fR \fI<THREADS>\fR \fB\-T\fR, \fB\-\-threads\fR
Number of threads to use Number of threads to use
.TP .TP
\fB\-\-config\fR \fI<FILE>\fR \fB\-\-config\fR=\fIFILE\fR
Specify a config file to use Specify a config file to use
.TP .TP
\fB\-n\fR, \fB\-\-number\-of\-lines\fR \fI<NUMBER>\fR \fB\-n\fR, \fB\-\-number\-of\-lines\fR=\fINUMBER\fR
Display the \*(Aqn\*(Aq largest entries. (Default is terminal_height) Number of lines of output to show. (Default is terminal_height \- 10)
.TP .TP
\fB\-p\fR, \fB\-\-full\-paths\fR \fB\-p\fR, \fB\-\-full\-paths\fR
Subdirectories will not have their path shortened Subdirectories will not have their path shortened
.TP .TP
\fB\-X\fR, \fB\-\-ignore\-directory\fR \fI<PATH>\fR \fB\-X\fR, \fB\-\-ignore\-directory\fR=\fIPATH\fR
Exclude any file or directory with this path Exclude any file or directory with this path
.TP .TP
\fB\-I\fR, \fB\-\-ignore\-all\-in\-file\fR \fI<FILE>\fR \fB\-I\fR, \fB\-\-ignore\-all\-in\-file\fR=\fIFILE\fR
Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by \-\-invert_filter Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by \-\-invert_filter
.TP .TP
\fB\-L\fR, \fB\-\-dereference\-links\fR \fB\-L\fR, \fB\-\-dereference\-links\fR
@@ -54,7 +54,7 @@ No percent bars or percentages will be displayed
\fB\-B\fR, \fB\-\-bars\-on\-right\fR \fB\-B\fR, \fB\-\-bars\-on\-right\fR
percent bars moved to right side of screen percent bars moved to right side of screen
.TP .TP
\fB\-z\fR, \fB\-\-min\-size\fR \fI<MIN_SIZE>\fR \fB\-z\fR, \fB\-\-min\-size\fR=\fIMIN_SIZE\fR
Minimum size file to include in output Minimum size file to include in output
.TP .TP
\fB\-R\fR, \fB\-\-screen\-reader\fR \fB\-R\fR, \fB\-\-screen\-reader\fR
@@ -66,108 +66,75 @@ No total row will be displayed
\fB\-f\fR, \fB\-\-filecount\fR \fB\-f\fR, \fB\-\-filecount\fR
Directory \*(Aqsize\*(Aq is number of child files instead of disk size Directory \*(Aqsize\*(Aq is number of child files instead of disk size
.TP .TP
\fB\-i\fR, \fB\-\-ignore\-hidden\fR \fB\-i\fR, \fB\-\-ignore_hidden\fR
Do not display hidden files Do not display hidden files
.TP .TP
\fB\-v\fR, \fB\-\-invert\-filter\fR \fI<REGEX>\fR \fB\-v\fR, \fB\-\-invert\-filter\fR=\fIREGEX\fR
Exclude filepaths matching this regex. To ignore png files type: \-v "\\.png$" Exclude filepaths matching this regex. To ignore png files type: \-v "\\.png$"
.TP .TP
\fB\-e\fR, \fB\-\-filter\fR \fI<REGEX>\fR \fB\-e\fR, \fB\-\-filter\fR=\fIREGEX\fR
Only include filepaths matching this regex. For png files type: \-e "\\.png$" Only include filepaths matching this regex. For png files type: \-e "\\.png$"
.TP .TP
\fB\-t\fR, \fB\-\-file\-types\fR \fB\-t\fR, \fB\-\-file_types\fR
show only these file types show only these file types
.TP .TP
\fB\-w\fR, \fB\-\-terminal\-width\fR \fI<WIDTH>\fR \fB\-w\fR, \fB\-\-terminal_width\fR=\fIWIDTH\fR
Specify width of output overriding the auto detection of terminal width Specify width of output overriding the auto detection of terminal width
.TP .TP
\fB\-P\fR, \fB\-\-no\-progress\fR \fB\-P\fR, \fB\-\-no\-progress\fR
Disable the progress indication Disable the progress indication.
.TP .TP
\fB\-\-print\-errors\fR \fB\-\-print\-errors\fR
Print path with errors Print path with errors.
.TP .TP
\fB\-D\fR, \fB\-\-only\-dir\fR \fB\-D\fR, \fB\-\-only\-dir\fR
Only directories will be displayed Only directories will be displayed.
.TP .TP
\fB\-F\fR, \fB\-\-only\-file\fR \fB\-F\fR, \fB\-\-only\-file\fR
Only files will be displayed. (Finds your largest files) Only files will be displayed. (Finds your largest files)
.TP .TP
\fB\-o\fR, \fB\-\-output\-format\fR \fI<FORMAT>\fR \fB\-o\fR, \fB\-\-output\-format\fR=\fIFORMAT\fR
Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.
.br .br
.br .br
\fIPossible values:\fR [\fIpossible values: \fRsi, b, k, m, g, t, kb, mb, gb, tb]
.RS 14
.IP \(bu 2
si: SI prefix (powers of 1000)
.IP \(bu 2
b: byte (B)
.IP \(bu 2
k: kibibyte (KiB)
.IP \(bu 2
m: mebibyte (MiB)
.IP \(bu 2
g: gibibyte (GiB)
.IP \(bu 2
t: tebibyte (TiB)
.IP \(bu 2
kb: kilobyte (kB)
.IP \(bu 2
mb: megabyte (MB)
.IP \(bu 2
gb: gigabyte (GB)
.IP \(bu 2
tb: terabyte (TB)
.RE
.TP .TP
\fB\-S\fR, \fB\-\-stack\-size\fR \fI<STACK_SIZE>\fR \fB\-S\fR, \fB\-\-stack\-size\fR=\fISTACK_SIZE\fR
Specify memory to use as stack size \- use if you see: \*(Aqfatal runtime error: stack overflow\*(Aq (default low memory=1048576, high memory=1073741824) Specify memory to use as stack size \- use if you see: \*(Aqfatal runtime error: stack overflow\*(Aq (default low memory=1048576, high memory=1073741824)
.TP .TP
\fB\-j\fR, \fB\-\-output\-json\fR \fB\-j\fR, \fB\-\-output\-json\fR
Output the directory tree as json to the current directory Output the directory tree as json to the current directory
.TP .TP
\fB\-M\fR, \fB\-\-mtime\fR \fI<MTIME>\fR \fB\-M\fR, \fB\-\-mtime\fR
+/\-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and \-n => (𝑐𝑢𝑟𝑟𝑛, +∞) +/\-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and \-n => (𝑐𝑢𝑟𝑟𝑛, +∞)
.TP .TP
\fB\-A\fR, \fB\-\-atime\fR \fI<ATIME>\fR \fB\-A\fR, \fB\-\-atime\fR
just like \-mtime, but based on file access time just like \-mtime, but based on file access time
.TP .TP
\fB\-y\fR, \fB\-\-ctime\fR \fI<CTIME>\fR \fB\-y\fR, \fB\-\-ctime\fR
just like \-mtime, but based on file change time just like \-mtime, but based on file change time
.TP .TP
\fB\-\-files0\-from\fR \fI<FILES0_FROM>\fR \fB\-\-files0\-from\fR
Read NUL\-terminated paths from FILE (use `\-` for stdin) run dust on NUL\-terminated file names specified in file; if argument is \-, then read names from standard input
.TP .TP
\fB\-\-files\-from\fR \fI<FILES_FROM>\fR \fB\-\-collapse\fR
Read newline\-terminated paths from FILE (use `\-` for stdin)
.TP
\fB\-\-collapse\fR \fI<COLLAPSE>\fR
Keep these directories collapsed Keep these directories collapsed
.TP .TP
\fB\-m\fR, \fB\-\-filetime\fR \fI<FILETIME>\fR \fB\-m\fR, \fB\-\-filetime\fR
Directory \*(Aqsize\*(Aq is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time Directory \*(Aqsize\*(Aq is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time
.br .br
.br .br
\fIPossible values:\fR [\fIpossible values: \fRa, c, m]
.RS 14
.IP \(bu 2
a: last accessed time
.IP \(bu 2
c: last changed time
.IP \(bu 2
m: last modified time
.RE
.TP .TP
\fB\-h\fR, \fB\-\-help\fR \fB\-h\fR, \fB\-\-help\fR
Print help (see a summary with \*(Aq\-h\*(Aq) Print help
.TP .TP
\fB\-V\fR, \fB\-\-version\fR \fB\-V\fR, \fB\-\-version\fR
Print version Print version
.TP .TP
[\fIPATH\fR] [\fIPATH\fR]
Input files or directories
.SH VERSION .SH VERSION
v1.2.3 v1.1.1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,261 +1,326 @@
use std::fmt; use clap::{builder::PossibleValue, value_parser, Arg, Command};
use clap::{Parser, ValueEnum, ValueHint};
// For single thread mode set this variable on your command line: // For single thread mode set this variable on your command line:
// export RAYON_NUM_THREADS=1 // export RAYON_NUM_THREADS=1
/// Like du but more intuitive pub fn build_cli() -> Command {
#[derive(Debug, Parser)] Command::new("Dust")
#[command(name("Dust"), version)] .about("Like du but more intuitive")
pub struct Cli { .version(env!("CARGO_PKG_VERSION"))
/// Depth to show .arg(
#[arg(short, long)] Arg::new("depth")
pub depth: Option<usize>, .short('d')
.long("depth")
/// Number of threads to use .value_name("DEPTH")
#[arg(short('T'), long)] .value_parser(value_parser!(usize))
pub threads: Option<usize>, .help("Depth to show")
.num_args(1)
/// Specify a config file to use )
#[arg(long, value_name("FILE"), value_hint(ValueHint::FilePath))] .arg(
pub config: Option<String>, Arg::new("threads")
.short('T')
/// Display the 'n' largest entries. (Default is terminal_height) .long("threads")
#[arg(short, long, value_name("NUMBER"))] .value_parser(value_parser!(usize))
pub number_of_lines: Option<usize>, .help("Number of threads to use")
.num_args(1)
/// Subdirectories will not have their path shortened )
#[arg(short('p'), long)] .arg(
pub full_paths: bool, Arg::new("config")
.long("config")
/// Exclude any file or directory with this path .help("Specify a config file to use")
#[arg(short('X'), long, value_name("PATH"), value_hint(ValueHint::AnyPath))] .value_name("FILE")
pub ignore_directory: Option<Vec<String>>, .value_hint(clap::ValueHint::FilePath)
.value_parser(value_parser!(String))
/// Exclude any file or directory with a regex matching that listed in this .num_args(1)
/// file, the file entries will be added to the ignore regexs provided by )
/// --invert_filter .arg(
#[arg(short('I'), long, value_name("FILE"), value_hint(ValueHint::FilePath))] Arg::new("number_of_lines")
pub ignore_all_in_file: Option<String>, .short('n')
.long("number-of-lines")
/// dereference sym links - Treat sym links as directories and go into them .value_name("NUMBER")
#[arg(short('L'), long)] .value_parser(value_parser!(usize))
pub dereference_links: bool, .help("Number of lines of output to show. (Default is terminal_height - 10)")
.num_args(1)
/// Only count the files and directories on the same filesystem as the )
/// supplied directory .arg(
#[arg(short('x'), long)] Arg::new("display_full_paths")
pub limit_filesystem: bool, .short('p')
.long("full-paths")
/// Use file length instead of blocks .action(clap::ArgAction::SetTrue)
#[arg(short('s'), long)] .help("Subdirectories will not have their path shortened"),
pub apparent_size: bool, )
.arg(
/// Print tree upside down (biggest highest) Arg::new("ignore_directory")
#[arg(short, long)] .short('X')
pub reverse: bool, .long("ignore-directory")
.value_name("PATH")
/// No colors will be printed (Useful for commands like: watch) .value_hint(clap::ValueHint::AnyPath)
#[arg(short('c'), long)] .action(clap::ArgAction::Append)
pub no_colors: bool, .help("Exclude any file or directory with this path"),
)
/// Force colors print .arg(
#[arg(short('C'), long)] Arg::new("ignore_all_in_file")
pub force_colors: bool, .short('I')
.long("ignore-all-in-file")
/// No percent bars or percentages will be displayed .value_name("FILE")
#[arg(short('b'), long)] .value_hint(clap::ValueHint::FilePath)
pub no_percent_bars: bool, .value_parser(value_parser!(String))
.help("Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter"),
/// percent bars moved to right side of screen )
#[arg(short('B'), long)] .arg(
pub bars_on_right: bool, Arg::new("dereference_links")
.short('L')
/// Minimum size file to include in output .long("dereference-links")
#[arg(short('z'), long)] .action(clap::ArgAction::SetTrue)
pub min_size: Option<String>, .help("dereference sym links - Treat sym links as directories and go into them"),
)
/// For screen readers. Removes bars. Adds new column: depth level (May want .arg(
/// to use -p too for full path) Arg::new("limit_filesystem")
#[arg(short('R'), long)] .short('x')
pub screen_reader: bool, .long("limit-filesystem")
.action(clap::ArgAction::SetTrue)
/// No total row will be displayed .help("Only count the files and directories on the same filesystem as the supplied directory"),
#[arg(long)] )
pub skip_total: bool, .arg(
Arg::new("display_apparent_size")
/// Directory 'size' is number of child files instead of disk size .short('s')
#[arg(short, long)] .long("apparent-size")
pub filecount: bool, .action(clap::ArgAction::SetTrue)
.help("Use file length instead of blocks"),
/// Do not display hidden files )
// Do not use 'h' this is used by 'help' .arg(
#[arg(short, long)] Arg::new("reverse")
pub ignore_hidden: bool, .short('r')
.long("reverse")
/// Exclude filepaths matching this regex. To ignore png files type: -v .action(clap::ArgAction::SetTrue)
/// "\.png$" .help("Print tree upside down (biggest highest)"),
#[arg( )
short('v'), .arg(
long, Arg::new("no_colors")
value_name("REGEX"), .short('c')
conflicts_with("filter"), .long("no-colors")
conflicts_with("file_types") .action(clap::ArgAction::SetTrue)
)] .help("No colors will be printed (Useful for commands like: watch)"),
pub invert_filter: Option<Vec<String>>, )
.arg(
/// Only include filepaths matching this regex. For png files type: -e Arg::new("force_colors")
/// "\.png$" .short('C')
#[arg(short('e'), long, value_name("REGEX"), conflicts_with("file_types"))] .long("force-colors")
pub filter: Option<Vec<String>>, .action(clap::ArgAction::SetTrue)
.help("Force colors print"),
/// show only these file types )
#[arg(short('t'), long, conflicts_with("depth"), conflicts_with("only_dir"))] .arg(
pub file_types: bool, Arg::new("no_bars")
.short('b')
/// Specify width of output overriding the auto detection of terminal width .long("no-percent-bars")
#[arg(short('w'), long, value_name("WIDTH"))] .action(clap::ArgAction::SetTrue)
pub terminal_width: Option<usize>, .help("No percent bars or percentages will be displayed"),
)
/// Disable the progress indication. .arg(
#[arg(short('P'), long)] Arg::new("bars_on_right")
pub no_progress: bool, .short('B')
.long("bars-on-right")
/// Print path with errors. .action(clap::ArgAction::SetTrue)
#[arg(long)] .help("percent bars moved to right side of screen"),
pub print_errors: bool, )
.arg(
/// Only directories will be displayed. Arg::new("min_size")
#[arg( .short('z')
short('D'), .long("min-size")
long, .value_name("MIN_SIZE")
conflicts_with("only_file"), .num_args(1)
conflicts_with("file_types") .help("Minimum size file to include in output"),
)] )
pub only_dir: bool, .arg(
Arg::new("screen_reader")
/// Only files will be displayed. (Finds your largest files) .short('R')
#[arg(short('F'), long, conflicts_with("only_dir"))] .long("screen-reader")
pub only_file: bool, .action(clap::ArgAction::SetTrue)
.help("For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)"),
/// Changes output display size. si will print sizes in powers of 1000. b k )
/// m g t kb mb gb tb will print the whole tree in that size. .arg(
#[arg(short, long, value_enum, value_name("FORMAT"), ignore_case(true))] Arg::new("skip_total")
pub output_format: Option<OutputFormat>, .long("skip-total")
.action(clap::ArgAction::SetTrue)
/// Specify memory to use as stack size - use if you see: 'fatal runtime .help("No total row will be displayed"),
/// error: stack overflow' (default low memory=1048576, high )
/// memory=1073741824) .arg(
#[arg(short('S'), long)] Arg::new("by_filecount")
pub stack_size: Option<usize>, .short('f')
.long("filecount")
/// Input files or directories. .action(clap::ArgAction::SetTrue)
#[arg(value_name("PATH"), value_hint(ValueHint::AnyPath))] .help("Directory 'size' is number of child files instead of disk size"),
pub params: Option<Vec<String>>, )
.arg(
/// Output the directory tree as json to the current directory Arg::new("ignore_hidden")
#[arg(short('j'), long)] .short('i') // Do not use 'h' this is used by 'help'
pub output_json: bool, .long("ignore_hidden")
.action(clap::ArgAction::SetTrue)
/// +/-n matches files modified more/less than n days ago , and n matches .help("Do not display hidden files"),
/// files modified exactly n days ago, days are rounded down.That is +n => )
/// (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞) .arg(
#[arg(short('M'), long, allow_hyphen_values(true))] Arg::new("invert_filter")
pub mtime: Option<String>, .short('v')
.long("invert-filter")
/// just like -mtime, but based on file access time .value_name("REGEX")
#[arg(short('A'), long, allow_hyphen_values(true))] .action(clap::ArgAction::Append)
pub atime: Option<String>, .conflicts_with("filter")
.conflicts_with("types")
/// just like -mtime, but based on file change time .help("Exclude filepaths matching this regex. To ignore png files type: -v \"\\.png$\" "),
#[arg(short('y'), long, allow_hyphen_values(true))] )
pub ctime: Option<String>, .arg(
Arg::new("filter")
/// Read NUL-terminated paths from FILE (use `-` for stdin). .short('e')
#[arg(long, value_hint(ValueHint::AnyPath), conflicts_with("files_from"))] .long("filter")
pub files0_from: Option<String>, .value_name("REGEX")
.action(clap::ArgAction::Append)
/// Read newline-terminated paths from FILE (use `-` for stdin). .conflicts_with("types")
#[arg(long, value_hint(ValueHint::AnyPath), conflicts_with("files0_from"))] .help("Only include filepaths matching this regex. For png files type: -e \"\\.png$\" "),
pub files_from: Option<String>, )
.arg(
/// Keep these directories collapsed Arg::new("types")
#[arg(long, value_hint(ValueHint::AnyPath))] .short('t')
pub collapse: Option<Vec<String>>, .long("file_types")
.conflicts_with("depth")
/// Directory 'size' is max filetime of child files instead of disk size. .conflicts_with("only_dir")
/// while a/c/m for last accessed/changed/modified time .action(clap::ArgAction::SetTrue)
#[arg(short('m'), long, value_enum)] .help("show only these file types"),
pub filetime: Option<FileTime>, )
} .arg(
Arg::new("width")
#[derive(Clone, Copy, Debug, ValueEnum)] .short('w')
#[value(rename_all = "lower")] .long("terminal_width")
pub enum OutputFormat { .value_name("WIDTH")
/// SI prefix (powers of 1000) .value_parser(value_parser!(usize))
SI, .num_args(1)
.help("Specify width of output overriding the auto detection of terminal width"),
/// byte (B) )
B, .arg(
Arg::new("disable_progress")
/// kibibyte (KiB) .short('P')
#[value(name = "k", alias("kib"))] .long("no-progress")
KiB, .action(clap::ArgAction::SetTrue)
.help("Disable the progress indication."),
/// mebibyte (MiB) )
#[value(name = "m", alias("mib"))] .arg(
MiB, Arg::new("print_errors")
.long("print-errors")
/// gibibyte (GiB) .action(clap::ArgAction::SetTrue)
#[value(name = "g", alias("gib"))] .help("Print path with errors."),
GiB, )
.arg(
/// tebibyte (TiB) Arg::new("only_dir")
#[value(name = "t", alias("tib"))] .short('D')
TiB, .long("only-dir")
.conflicts_with("only_file")
/// kilobyte (kB) .conflicts_with("types")
KB, .action(clap::ArgAction::SetTrue)
.help("Only directories will be displayed."),
/// megabyte (MB) )
MB, .arg(
Arg::new("only_file")
/// gigabyte (GB) .short('F')
GB, .long("only-file")
.conflicts_with("only_dir")
/// terabyte (TB) .action(clap::ArgAction::SetTrue)
TB, .help("Only files will be displayed. (Finds your largest files)"),
} )
.arg(
impl fmt::Display for OutputFormat { Arg::new("output_format")
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { .short('o')
match self { .long("output-format")
Self::SI => write!(f, "si"), .value_name("FORMAT")
Self::B => write!(f, "b"), .value_parser([
Self::KiB => write!(f, "k"), PossibleValue::new("si"),
Self::MiB => write!(f, "m"), PossibleValue::new("b"),
Self::GiB => write!(f, "g"), PossibleValue::new("k").alias("kib"),
Self::TiB => write!(f, "t"), PossibleValue::new("m").alias("mib"),
Self::KB => write!(f, "kb"), PossibleValue::new("g").alias("gib"),
Self::MB => write!(f, "mb"), PossibleValue::new("t").alias("tib"),
Self::GB => write!(f, "gb"), PossibleValue::new("kb"),
Self::TB => write!(f, "tb"), PossibleValue::new("mb"),
} PossibleValue::new("gb"),
} PossibleValue::new("tb"),
} ])
.ignore_case(true)
#[derive(Clone, Copy, Debug, ValueEnum)] .help("Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.")
pub enum FileTime { )
/// last accessed time .arg(
#[value(name = "a", alias("accessed"))] Arg::new("stack_size")
Accessed, .short('S')
.long("stack-size")
/// last changed time .value_name("STACK_SIZE")
#[value(name = "c", alias("changed"))] .value_parser(value_parser!(usize))
Changed, .num_args(1)
.help("Specify memory to use as stack size - use if you see: 'fatal runtime error: stack overflow' (default low memory=1048576, high memory=1073741824)"),
/// last modified time )
#[value(name = "m", alias("modified"))] .arg(
Modified, Arg::new("params")
.value_name("PATH")
.value_hint(clap::ValueHint::AnyPath)
.value_parser(value_parser!(String))
.num_args(1..)
)
.arg(
Arg::new("output_json")
.short('j')
.long("output-json")
.action(clap::ArgAction::SetTrue)
.help("Output the directory tree as json to the current directory"),
)
.arg(
Arg::new("mtime")
.short('M')
.long("mtime")
.num_args(1)
.allow_hyphen_values(true)
.value_parser(value_parser!(String))
.help("+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (−∞, curr(n+1)), n => [curr(n+1), currn), and -n => (𝑐𝑢𝑟𝑟𝑛, +∞)")
)
.arg(
Arg::new("atime")
.short('A')
.long("atime")
.num_args(1)
.allow_hyphen_values(true)
.value_parser(value_parser!(String))
.help("just like -mtime, but based on file access time")
)
.arg(
Arg::new("ctime")
.short('y')
.long("ctime")
.num_args(1)
.allow_hyphen_values(true)
.value_parser(value_parser!(String))
.help("just like -mtime, but based on file change time")
)
.arg(
Arg::new("files0_from")
.long("files0-from")
.value_hint(clap::ValueHint::AnyPath)
.value_parser(value_parser!(String))
.num_args(1)
.help("run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input"),
)
.arg(
Arg::new("collapse")
.long("collapse")
.value_hint(clap::ValueHint::AnyPath)
.value_parser(value_parser!(String))
.action(clap::ArgAction::Append)
.help("Keep these directories collapsed"),
)
.arg(
Arg::new("filetime")
.short('m')
.long("filetime")
.num_args(1)
.value_parser([
PossibleValue::new("a").alias("accessed"),
PossibleValue::new("c").alias("changed"),
PossibleValue::new("m").alias("modified"),
])
.help("Directory 'size' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time"),
)
} }

View File

@@ -1,12 +1,13 @@
use crate::node::FileTime; use crate::node::FileTime;
use chrono::{Local, TimeZone}; use chrono::{Local, TimeZone};
use clap::ArgMatches;
use config_file::FromConfigFile; use config_file::FromConfigFile;
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use std::io::IsTerminal;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use crate::cli::Cli;
use crate::dir_walker::Operator; use crate::dir_walker::Operator;
use crate::display::get_number_format; use crate::display::get_number_format;
@@ -36,81 +37,82 @@ pub struct Config {
pub output_json: Option<bool>, pub output_json: Option<bool>,
pub print_errors: Option<bool>, pub print_errors: Option<bool>,
pub files0_from: Option<String>, pub files0_from: Option<String>,
pub number_of_lines: Option<usize>,
pub files_from: Option<String>,
} }
impl Config { impl Config {
pub fn get_files0_from(&self, options: &Cli) -> Option<String> { pub fn get_files_from(&self, options: &ArgMatches) -> Option<String> {
let from_file = &options.files0_from; let from_file = options.get_one::<String>("files0_from");
match from_file { match from_file {
None => self.files0_from.as_ref().map(|x| x.to_string()), None => self.files0_from.as_ref().map(|x| x.to_string()),
Some(x) => Some(x.to_string()), Some(x) => Some(x.to_string()),
} }
} }
pub fn get_no_colors(&self, options: &ArgMatches) -> bool {
pub fn get_files_from(&self, options: &Cli) -> Option<String> { Some(true) == self.no_colors || options.get_flag("no_colors")
let from_file = &options.files_from;
match from_file {
None => self.files_from.as_ref().map(|x| x.to_string()),
Some(x) => Some(x.to_string()),
}
} }
pub fn get_no_colors(&self, options: &Cli) -> bool { pub fn get_force_colors(&self, options: &ArgMatches) -> bool {
Some(true) == self.no_colors || options.no_colors Some(true) == self.force_colors || options.get_flag("force_colors")
} }
pub fn get_force_colors(&self, options: &Cli) -> bool { pub fn get_disable_progress(&self, options: &ArgMatches) -> bool {
Some(true) == self.force_colors || options.force_colors Some(true) == self.disable_progress
|| options.get_flag("disable_progress")
|| !std::io::stdout().is_terminal()
} }
pub fn get_disable_progress(&self, options: &Cli) -> bool { pub fn get_apparent_size(&self, options: &ArgMatches) -> bool {
Some(true) == self.disable_progress || options.no_progress Some(true) == self.display_apparent_size || options.get_flag("display_apparent_size")
} }
pub fn get_apparent_size(&self, options: &Cli) -> bool { pub fn get_ignore_hidden(&self, options: &ArgMatches) -> bool {
Some(true) == self.display_apparent_size || options.apparent_size Some(true) == self.ignore_hidden || options.get_flag("ignore_hidden")
} }
pub fn get_ignore_hidden(&self, options: &Cli) -> bool { pub fn get_full_paths(&self, options: &ArgMatches) -> bool {
Some(true) == self.ignore_hidden || options.ignore_hidden Some(true) == self.display_full_paths || options.get_flag("display_full_paths")
} }
pub fn get_full_paths(&self, options: &Cli) -> bool { pub fn get_reverse(&self, options: &ArgMatches) -> bool {
Some(true) == self.display_full_paths || options.full_paths Some(true) == self.reverse || options.get_flag("reverse")
} }
pub fn get_reverse(&self, options: &Cli) -> bool { pub fn get_no_bars(&self, options: &ArgMatches) -> bool {
Some(true) == self.reverse || options.reverse Some(true) == self.no_bars || options.get_flag("no_bars")
} }
pub fn get_no_bars(&self, options: &Cli) -> bool { pub fn get_output_format(&self, options: &ArgMatches) -> String {
Some(true) == self.no_bars || options.no_percent_bars let out_fmt = options.get_one::<String>("output_format");
}
pub fn get_output_format(&self, options: &Cli) -> String {
let out_fmt = options.output_format;
(match out_fmt { (match out_fmt {
None => match &self.output_format { None => match &self.output_format {
None => "".to_string(), None => "".to_string(),
Some(x) => x.to_string(), Some(x) => x.to_string(),
}, },
Some(x) => x.to_string(), Some(x) => x.into(),
}) })
.to_lowercase() .to_lowercase()
} }
pub fn get_filetime(&self, options: &Cli) -> Option<FileTime> { pub fn get_filetime(&self, options: &ArgMatches) -> Option<FileTime> {
options.filetime.map(FileTime::from) let out_fmt = options.get_one::<String>("filetime");
match out_fmt {
None => None,
Some(x) => match x.as_str() {
"m" | "modified" => Some(FileTime::Modified),
"a" | "accessed" => Some(FileTime::Accessed),
"c" | "changed" => Some(FileTime::Changed),
_ => unreachable!(),
},
}
} }
pub fn get_skip_total(&self, options: &Cli) -> bool { pub fn get_skip_total(&self, options: &ArgMatches) -> bool {
Some(true) == self.skip_total || options.skip_total Some(true) == self.skip_total || options.get_flag("skip_total")
} }
pub fn get_screen_reader(&self, options: &Cli) -> bool { pub fn get_screen_reader(&self, options: &ArgMatches) -> bool {
Some(true) == self.screen_reader || options.screen_reader Some(true) == self.screen_reader || options.get_flag("screen_reader")
} }
pub fn get_depth(&self, options: &Cli) -> usize { pub fn get_depth(&self, options: &ArgMatches) -> usize {
if let Some(v) = options.depth { if let Some(v) = options.get_one::<usize>("depth") {
return v; return *v;
} }
self.depth.unwrap_or(usize::MAX) self.depth.unwrap_or(usize::MAX)
} }
pub fn get_min_size(&self, options: &Cli) -> Option<usize> { pub fn get_min_size(&self, options: &ArgMatches) -> Option<usize> {
let size_from_param = options.min_size.as_ref(); let size_from_param = options.get_one::<String>("min_size");
self._get_min_size(size_from_param) self._get_min_size(size_from_param)
} }
fn _get_min_size(&self, min_size: Option<&String>) -> Option<usize> { fn _get_min_size(&self, min_size: Option<&String>) -> Option<usize> {
@@ -124,58 +126,58 @@ impl Config {
size_from_param size_from_param
} }
} }
pub fn get_only_dir(&self, options: &Cli) -> bool { pub fn get_only_dir(&self, options: &ArgMatches) -> bool {
Some(true) == self.only_dir || options.only_dir Some(true) == self.only_dir || options.get_flag("only_dir")
} }
pub fn get_print_errors(&self, options: &Cli) -> bool { pub fn get_print_errors(&self, options: &ArgMatches) -> bool {
Some(true) == self.print_errors || options.print_errors Some(true) == self.print_errors || options.get_flag("print_errors")
} }
pub fn get_only_file(&self, options: &Cli) -> bool { pub fn get_only_file(&self, options: &ArgMatches) -> bool {
Some(true) == self.only_file || options.only_file Some(true) == self.only_file || options.get_flag("only_file")
} }
pub fn get_bars_on_right(&self, options: &Cli) -> bool { pub fn get_bars_on_right(&self, options: &ArgMatches) -> bool {
Some(true) == self.bars_on_right || options.bars_on_right Some(true) == self.bars_on_right || options.get_flag("bars_on_right")
} }
pub fn get_custom_stack_size(&self, options: &Cli) -> Option<usize> { pub fn get_custom_stack_size(&self, options: &ArgMatches) -> Option<usize> {
let from_cmd_line = options.stack_size; let from_cmd_line = options.get_one::<usize>("stack_size");
if from_cmd_line.is_none() { if from_cmd_line.is_none() {
self.stack_size self.stack_size
} else { } else {
from_cmd_line from_cmd_line.copied()
} }
} }
pub fn get_threads(&self, options: &Cli) -> Option<usize> { pub fn get_threads(&self, options: &ArgMatches) -> Option<usize> {
let from_cmd_line = options.threads; let from_cmd_line = options.get_one::<usize>("threads");
if from_cmd_line.is_none() { if from_cmd_line.is_none() {
self.threads self.threads
} else { } else {
from_cmd_line from_cmd_line.copied()
} }
} }
pub fn get_output_json(&self, options: &Cli) -> bool { pub fn get_output_json(&self, options: &ArgMatches) -> bool {
Some(true) == self.output_json || options.output_json Some(true) == self.output_json || options.get_flag("output_json")
} }
pub fn get_number_of_lines(&self, options: &Cli) -> Option<usize> { pub fn get_modified_time_operator(&self, options: &ArgMatches) -> Option<(Operator, i64)> {
let from_cmd_line = options.number_of_lines; get_filter_time_operator(
if from_cmd_line.is_none() { options.get_one::<String>("mtime"),
self.number_of_lines get_current_date_epoch_seconds(),
} else { )
from_cmd_line
}
} }
pub fn get_modified_time_operator(&self, options: &Cli) -> Option<(Operator, i64)> { pub fn get_accessed_time_operator(&self, options: &ArgMatches) -> Option<(Operator, i64)> {
get_filter_time_operator(options.mtime.as_ref(), get_current_date_epoch_seconds()) get_filter_time_operator(
options.get_one::<String>("atime"),
get_current_date_epoch_seconds(),
)
} }
pub fn get_accessed_time_operator(&self, options: &Cli) -> Option<(Operator, i64)> { pub fn get_changed_time_operator(&self, options: &ArgMatches) -> Option<(Operator, i64)> {
get_filter_time_operator(options.atime.as_ref(), get_current_date_epoch_seconds()) get_filter_time_operator(
} options.get_one::<String>("ctime"),
get_current_date_epoch_seconds(),
pub fn get_changed_time_operator(&self, options: &Cli) -> Option<(Operator, i64)> { )
get_filter_time_operator(options.ctime.as_ref(), get_current_date_epoch_seconds())
} }
} }
@@ -244,17 +246,17 @@ fn convert_min_size(input: &str) -> Option<usize> {
} }
} }
fn get_config_locations(base: PathBuf) -> Vec<PathBuf> { fn get_config_locations(base: &Path) -> Vec<PathBuf> {
vec![ vec![
base.join(".dust.toml"), base.join(".dust.toml"),
base.join(".config").join("dust").join("config.toml"), base.join(".config").join("dust").join("config.toml"),
] ]
} }
pub fn get_config(conf_path: Option<&String>) -> Config { pub fn get_config(conf_path: Option<String>) -> Config {
match conf_path { match conf_path {
Some(path_str) => { Some(path_str) => {
let path = Path::new(path_str); let path = Path::new(&path_str);
if path.exists() { if path.exists() {
match Config::from_config_file(path) { match Config::from_config_file(path) {
Ok(config) => return config, Ok(config) => return config,
@@ -267,12 +269,12 @@ pub fn get_config(conf_path: Option<&String>) -> Config {
} }
} }
None => { None => {
if let Some(home) = std::env::home_dir() { if let Some(home) = directories::BaseDirs::new() {
for path in get_config_locations(home) { for path in get_config_locations(home.home_dir()) {
if path.exists() if path.exists() {
&& let Ok(config) = Config::from_config_file(&path) if let Ok(config) = Config::from_config_file(&path) {
{ return config;
return config; }
} }
} }
} }
@@ -288,7 +290,8 @@ mod tests {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
use clap::Parser; use clap::builder::PossibleValue;
use clap::{value_parser, Arg, ArgMatches, Command};
#[test] #[test]
fn test_get_current_date_epoch_seconds() { fn test_get_current_date_epoch_seconds() {
@@ -357,8 +360,15 @@ mod tests {
assert_eq!(c.get_depth(&args), 5); assert_eq!(c.get_depth(&args), 5);
} }
fn get_args(args: Vec<&str>) -> Cli { fn get_args(args: Vec<&str>) -> ArgMatches {
Cli::parse_from(args) Command::new("Dust")
.arg(
Arg::new("depth")
.long("depth")
.num_args(1)
.value_parser(value_parser!(usize)),
)
.get_matches_from(args)
} }
#[test] #[test]
@@ -396,36 +406,20 @@ mod tests {
assert_eq!(c.get_filetime(&args), Some(FileTime::Changed)); assert_eq!(c.get_filetime(&args), Some(FileTime::Changed));
} }
fn get_filetime_args(args: Vec<&str>) -> Cli { fn get_filetime_args(args: Vec<&str>) -> ArgMatches {
Cli::parse_from(args) Command::new("Dust")
} .arg(
Arg::new("filetime")
#[test] .short('m')
fn test_get_number_of_lines() { .long("filetime")
// No config and no flag. .num_args(1)
let c = Config::default(); .value_parser([
let args = get_args(vec![]); PossibleValue::new("a").alias("accessed"),
assert_eq!(c.get_number_of_lines(&args), None); PossibleValue::new("c").alias("changed"),
PossibleValue::new("m").alias("modified"),
// Config is not defined and flag is defined. ])
let c = Config::default(); .help("Directory 'size' is max filetime of child files instead of disk size. while a/c/m for accessed/changed/modified time"),
let args = get_args(vec!["dust", "--number-of-lines", "5"]); )
assert_eq!(c.get_number_of_lines(&args), Some(5)); .get_matches_from(args)
// Config is defined and flag is not defined.
let c = Config {
number_of_lines: Some(3),
..Default::default()
};
let args = get_args(vec![]);
assert_eq!(c.get_number_of_lines(&args), Some(3));
// Both config and flag are defined.
let c = Config {
number_of_lines: Some(3),
..Default::default()
};
let args = get_args(vec!["dust", "--number-of-lines", "5"]);
assert_eq!(c.get_number_of_lines(&args), Some(5));
} }
} }

View File

@@ -5,10 +5,10 @@ use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use crate::node::Node; use crate::node::Node;
use crate::progress::ORDERING;
use crate::progress::Operation; use crate::progress::Operation;
use crate::progress::PAtomicInfo; use crate::progress::PAtomicInfo;
use crate::progress::RuntimeErrors; use crate::progress::RuntimeErrors;
use crate::progress::ORDERING;
use crate::utils::is_filtered_out_due_to_file_time; use crate::utils::is_filtered_out_due_to_file_time;
use crate::utils::is_filtered_out_due_to_invert_regex; 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;
@@ -69,11 +69,12 @@ pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: &WalkData) -> Vec<Node> {
// Remove files which have the same inode, we don't want to double count them. // Remove files which have the same inode, we don't want to double count them.
fn clean_inodes(x: Node, inodes: &mut HashSet<(u64, u64)>, walk_data: &WalkData) -> Option<Node> { fn clean_inodes(x: Node, inodes: &mut HashSet<(u64, u64)>, walk_data: &WalkData) -> Option<Node> {
if !walk_data.use_apparent_size if !walk_data.use_apparent_size {
&& let Some(id) = x.inode_device if let Some(id) = x.inode_device {
&& !inodes.insert(id) if !inodes.insert(id) {
{ return None;
return None; }
}
} }
// Sort Nodes so iteration order is predictable // Sort Nodes so iteration order is predictable
@@ -124,41 +125,17 @@ fn sort_by_inode(a: &Node, b: &Node) -> std::cmp::Ordering {
} }
} }
// Check if `path` is inside ignored directory
fn is_ignored_path(path: &Path, walk_data: &WalkData) -> bool {
if walk_data.ignore_directories.contains(path) {
return true;
}
// Entry is inside an ignored absolute path
// Absolute paths should be canonicalized before being added to `WalkData.ignore_directories`
for ignored_path in walk_data.ignore_directories.iter() {
if !ignored_path.is_absolute() {
continue;
}
let absolute_entry_path = std::fs::canonicalize(path).unwrap_or_default();
if absolute_entry_path.starts_with(ignored_path) {
return true;
}
}
false
}
fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool { fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
if is_ignored_path(&entry.path(), walk_data) {
return true;
}
let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.'); let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.');
let is_ignored_path = walk_data.ignore_directories.contains(&entry.path());
let follow_links = walk_data.follow_links && entry.file_type().is_ok_and(|ft| ft.is_symlink()); let follow_links = walk_data.follow_links && entry.file_type().is_ok_and(|ft| ft.is_symlink());
if !walk_data.allowed_filesystems.is_empty() { if !walk_data.allowed_filesystems.is_empty() {
let size_inode_device = get_metadata(entry.path(), false, follow_links); let size_inode_device = get_metadata(entry.path(), false, follow_links);
if let Some((_size, Some((_id, dev)), _gunk)) = size_inode_device if let Some((_size, Some((_id, dev)), _gunk)) = size_inode_device {
&& !walk_data.allowed_filesystems.contains(&dev) if !walk_data.allowed_filesystems.contains(&dev) {
{ return true;
return true; }
} }
} }
if walk_data.filter_accessed_time.is_some() if walk_data.filter_accessed_time.is_some()
@@ -166,19 +143,20 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
|| walk_data.filter_changed_time.is_some() || walk_data.filter_changed_time.is_some()
{ {
let size_inode_device = get_metadata(entry.path(), false, follow_links); let size_inode_device = get_metadata(entry.path(), false, follow_links);
if let Some((_, _, (modified_time, accessed_time, changed_time))) = size_inode_device if let Some((_, _, (modified_time, accessed_time, changed_time))) = size_inode_device {
&& entry.path().is_file() if entry.path().is_file()
&& [ && [
(&walk_data.filter_modified_time, modified_time), (&walk_data.filter_modified_time, modified_time),
(&walk_data.filter_accessed_time, accessed_time), (&walk_data.filter_accessed_time, accessed_time),
(&walk_data.filter_changed_time, changed_time), (&walk_data.filter_changed_time, changed_time),
] ]
.iter() .iter()
.any(|(filter_time, actual_time)| { .any(|(filter_time, actual_time)| {
is_filtered_out_due_to_file_time(filter_time, *actual_time) is_filtered_out_due_to_file_time(filter_time, *actual_time)
}) })
{ {
return true; return true;
}
} }
} }
@@ -197,13 +175,17 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
return true; return true;
} }
is_dot_file && walk_data.ignore_hidden (is_dot_file && walk_data.ignore_hidden) || is_ignored_path
} }
fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> { fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
let prog_data = &walk_data.progress_data; let prog_data = &walk_data.progress_data;
let errors = &walk_data.errors; let errors = &walk_data.errors;
if errors.lock().unwrap().abort {
return None;
}
let children = if dir.is_dir() { let children = if dir.is_dir() {
let read_dir = fs::read_dir(&dir); let read_dir = fs::read_dir(&dir);
match read_dir { match read_dir {
@@ -220,30 +202,32 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
// return walk(entry.path(), walk_data, depth) // return walk(entry.path(), walk_data, depth)
if !ignore_file(entry, walk_data) if !ignore_file(entry, walk_data) {
&& let Ok(data) = entry.file_type() if let Ok(data) = entry.file_type() {
{ if data.is_dir()
if data.is_dir() || (walk_data.follow_links && data.is_symlink())
|| (walk_data.follow_links && data.is_symlink()) {
{ return walk(entry.path(), walk_data, depth + 1);
return walk(entry.path(), walk_data, depth + 1); }
let node = build_node(
entry.path(),
vec![],
data.is_symlink(),
data.is_file(),
depth,
walk_data,
);
prog_data.num_files.fetch_add(1, ORDERING);
if let Some(ref file) = node {
prog_data
.total_file_size
.fetch_add(file.size, ORDERING);
}
return node;
} }
let node = build_node(
entry.path(),
vec![],
data.is_symlink(),
data.is_file(),
depth,
walk_data,
);
prog_data.num_files.fetch_add(1, ORDERING);
if let Some(ref file) = node {
prog_data.total_file_size.fetch_add(file.size, ORDERING);
}
return node;
} }
} }
Err(ref failed) => { Err(ref failed) => {
@@ -300,10 +284,9 @@ fn handle_error_and_retry(failed: &Error, dir: &Path, walk_data: &WalkData) -> b
editable_error.file_not_found.insert(failed.to_string()); editable_error.file_not_found.insert(failed.to_string());
} }
std::io::ErrorKind::Interrupted => { std::io::ErrorKind::Interrupted => {
let mut editable_error = walk_data.errors.lock().unwrap();
editable_error.interrupted_error += 1; editable_error.interrupted_error += 1;
// This does happen on some systems. It was set to 3 but sometimes dust runs would exceed this if editable_error.interrupted_error > 3 {
// However, if there is no limit this results in infinite retrys and dust never finishes
if editable_error.interrupted_error > 999 {
panic!("Multiple Interrupted Errors occurred while scanning filesystem. Aborting"); panic!("Multiple Interrupted Errors occurred while scanning filesystem. Aborting");
} else { } else {
return true; return true;

View File

@@ -11,8 +11,13 @@ use stfu8::encode_u8;
use chrono::{DateTime, Local, TimeZone, Utc}; use chrono::{DateTime, Local, TimeZone, Utc};
use std::cmp::max; use std::cmp::max;
use std::cmp::min; use std::cmp::min;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::fs; use std::fs;
use std::iter::repeat_n; use std::hash::Hash;
use std::iter::repeat;
use std::os::unix::ffi::OsStrExt;
use std::path::Path; use std::path::Path;
use thousands::Separable; use thousands::Separable;
@@ -37,6 +42,7 @@ pub struct DisplayData {
pub base_size: u64, pub base_size: u64,
pub longest_string_length: usize, pub longest_string_length: usize,
pub ls_colors: LsColors, pub ls_colors: LsColors,
pub duplicate_names: HashMap<String, u32>,
} }
impl DisplayData { impl DisplayData {
@@ -71,7 +77,11 @@ impl DisplayData {
fn percent_size(&self, node: &DisplayNode) -> f32 { fn percent_size(&self, node: &DisplayNode) -> f32 {
let result = node.size as f32 / self.base_size as f32; let result = node.size as f32 / self.base_size as f32;
if result.is_normal() { result } else { 0.0 } if result.is_normal() {
result
} else {
0.0
}
} }
} }
@@ -125,11 +135,14 @@ impl DrawData<'_> {
pub fn draw_it( pub fn draw_it(
idd: InitialDisplayData, idd: InitialDisplayData,
root_node: &DisplayNode,
no_percent_bars: bool, no_percent_bars: bool,
terminal_width: usize, terminal_width: usize,
root_node: &DisplayNode,
skip_total: bool, skip_total: bool,
) { ) {
let duplicate_names = check_for_dup_names(&root_node);
let num_chars_needed_on_left_most = if idd.by_filecount { let num_chars_needed_on_left_most = if idd.by_filecount {
let max_size = root_node.size; let max_size = root_node.size;
max_size.separate_with_commas().chars().count() max_size.separate_with_commas().chars().count()
@@ -144,10 +157,12 @@ pub fn draw_it(
"Not enough terminal width" "Not enough terminal width"
); );
// let duplicate_dir_names = find_duplicate_names(root_node, idd.short_paths);
let allowed_width = terminal_width - num_chars_needed_on_left_most - 2; let allowed_width = terminal_width - num_chars_needed_on_left_most - 2;
let num_indent_chars = 3; let num_indent_chars = 3;
let longest_string_length = let longest_string_length =
find_longest_dir_name(root_node, num_indent_chars, allowed_width, &idd); find_longest_dir_name(root_node, num_indent_chars, allowed_width, &idd, &duplicate_names);
let max_bar_length = if no_percent_bars || longest_string_length + 7 >= allowed_width { let max_bar_length = if no_percent_bars || longest_string_length + 7 >= allowed_width {
0 0
@@ -155,7 +170,7 @@ pub fn draw_it(
allowed_width - longest_string_length - 7 allowed_width - longest_string_length - 7
}; };
let first_size_bar = repeat_n(BLOCKS[0], max_bar_length).collect(); let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect();
let display_data = DisplayData { let display_data = DisplayData {
initial: idd, initial: idd,
@@ -163,6 +178,7 @@ pub fn draw_it(
base_size: root_node.size, base_size: root_node.size,
longest_string_length, longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(), ls_colors: LsColors::from_env().unwrap_or_default(),
duplicate_names
}; };
let draw_data = DrawData { let draw_data = DrawData {
indent: "".to_string(), indent: "".to_string(),
@@ -183,6 +199,82 @@ pub fn draw_it(
} }
} }
} }
fn check_for_dup_names(result:&DisplayNode) -> HashMap<String, u32> {
let mut names = HashMap::new();
let mut dup_names = HashMap::new();
// let empty = HashSet::new();
let mut results = VecDeque::new();
results.push_back((result, 0));
while results.len() > 0 {
let (current, level) = results.pop_front().unwrap();
let mut folders = current.name.iter().rev();
let mut s = String::new();
// Look at parent folder names - if they differ and we are printing them
// we dont need the helper
for _ in 0..level {
s.push_str( &encode_u8(folders.next().unwrap().as_bytes()));
}
if names.contains_key(&s){
// TODO: compare s with names[s]
// and walk back until you find a difference.
dup_names.insert(s, level);
} else {
names.insert(s, vec![&current.name]);
}
current.children.iter().for_each(|node| {results.push_back((&node, level+1));});
}
println!("{:?}", names);
println!("{:?}", dup_names);
dup_names
}
pub fn get_printable_name(node: &DisplayNode, short_paths: bool, dup_names: &HashMap<String, u32>) -> String {
let dir_name = &node.name;
let printable_name = {
if short_paths {
match dir_name.parent() {
Some(prefix) => match dir_name.strip_prefix(prefix) {
Ok(base) => base,
Err(_) => dir_name,
},
None => dir_name,
}
} else {
dir_name
}
};
let core = encode_u8(printable_name.display().to_string().as_bytes());
if dup_names.contains_key(&core) {
let level = dup_names[&core];
let mut folders = node.name.iter().rev();
folders.next();
let mut extra = VecDeque::new();
for _ in (0..level){
extra.push_back( encode_u8(folders.next().unwrap().as_bytes()) );
}
let h = extra.iter().fold(String::new(), |acc, entry| {
acc + entry
});
// let helper = extra.make_contiguous().iter().collect::<Vec<&String>>();
// let h = helper.join("/");
// let mut folders = dir_name.iter().rev(); //.next().next().unwrap();
// folders.next();
// let par = encode_u8(folders.next().unwrap().as_bytes());
format!("{core} ({h})")
} else {
core
}
}
fn find_biggest_size_str(node: &DisplayNode, output_format: &str) -> usize { fn find_biggest_size_str(node: &DisplayNode, output_format: &str) -> usize {
let mut mx = human_readable_number(node.size, output_format) let mut mx = human_readable_number(node.size, output_format)
@@ -199,8 +291,9 @@ fn find_longest_dir_name(
indent: usize, indent: usize,
terminal: usize, terminal: usize,
idd: &InitialDisplayData, idd: &InitialDisplayData,
dup_names: &HashMap<String, u32>,
) -> usize { ) -> usize {
let printable_name = get_printable_name(&node.name, idd.short_paths); let printable_name = get_printable_name(&node, idd.short_paths, dup_names);
let longest = if idd.is_screen_reader { let longest = if idd.is_screen_reader {
UnicodeWidthStr::width(&*printable_name) + 1 UnicodeWidthStr::width(&*printable_name) + 1
@@ -214,7 +307,7 @@ fn find_longest_dir_name(
// each none root tree drawing is 2 more chars, hence we increment indent by 2 // each none root tree drawing is 2 more chars, hence we increment indent by 2
node.children node.children
.iter() .iter()
.map(|c| find_longest_dir_name(c, indent + 2, terminal, idd)) .map(|c| find_longest_dir_name(c, indent + 2, terminal, idd, dup_names))
.fold(longest, max) .fold(longest, max)
} }
@@ -269,26 +362,8 @@ fn clean_indentation_string(s: &str) -> String {
is is
} }
pub fn get_printable_name<P: AsRef<Path>>(dir_name: &P, short_paths: bool) -> String {
let dir_name = dir_name.as_ref();
let printable_name = {
if short_paths {
match dir_name.parent() {
Some(prefix) => match dir_name.strip_prefix(prefix) {
Ok(base) => base,
Err(_) => dir_name,
},
None => dir_name,
}
} else {
dir_name
}
};
encode_u8(printable_name.display().to_string().as_bytes())
}
fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &DisplayData) -> String { fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &DisplayData) -> String {
let name = get_printable_name(&node.name, display_data.initial.short_paths); let name = get_printable_name(&node, display_data.initial.short_paths, &display_data.duplicate_names);
let indent_and_name = format!("{indent} {name}"); let indent_and_name = format!("{indent} {name}");
let width = UnicodeWidthStr::width(&*indent_and_name); let width = UnicodeWidthStr::width(&*indent_and_name);
@@ -298,9 +373,12 @@ fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &Display
); );
// Add spaces after the filename so we can draw the % used bar chart. // Add spaces after the filename so we can draw the % used bar chart.
name + " " let name_and_padding = name
.repeat(display_data.longest_string_length - width) + " "
.as_str() .repeat(display_data.longest_string_length - width)
.as_str();
name_and_padding
} }
fn maybe_trim_filename(name_in: String, indent: &str, display_data: &DisplayData) -> String { fn maybe_trim_filename(name_in: String, indent: &str, display_data: &DisplayData) -> String {
@@ -360,7 +438,7 @@ fn get_name_percent(
let name_and_padding = pad_or_trim_filename(node, indent, display_data); let name_and_padding = pad_or_trim_filename(node, indent, display_data);
(percents, name_and_padding) (percents, name_and_padding)
} else { } else {
let n = get_printable_name(&node.name, display_data.initial.short_paths); let n = get_printable_name(&node, display_data.initial.short_paths, &display_data.duplicate_names);
let name = maybe_trim_filename(n, indent, display_data); let name = maybe_trim_filename(n, indent, display_data);
("".into(), name) ("".into(), name)
} }
@@ -403,7 +481,7 @@ fn get_pretty_name(
.ls_colors .ls_colors
.style_for_path_with_metadata(&node.name, meta_result.as_ref().ok()); .style_for_path_with_metadata(&node.name, meta_result.as_ref().ok());
let ansi_style = directory_color let ansi_style = directory_color
.map(Style::to_nu_ansi_term_style) .map(Style::to_ansi_term_style)
.unwrap_or_default(); .unwrap_or_default();
let out = ansi_style.paint(name_and_padding); let out = ansi_style.paint(name_and_padding);
format!("{out}") format!("{out}")
@@ -439,9 +517,6 @@ pub fn get_number_format(output_str: &str) -> Option<(u64, char)> {
} }
pub fn human_readable_number(size: u64, output_str: &str) -> String { pub fn human_readable_number(size: u64, output_str: &str) -> String {
if output_str == "count" {
return size.to_string();
};
match get_number_format(output_str) { match get_number_format(output_str) {
Some((x, u)) => { Some((x, u)) => {
format!("{}{}", (size / x), u) format!("{}{}", (size / x), u)
@@ -542,13 +617,6 @@ mod tests {
assert_eq!(s, "short 3 4.0K 100%"); assert_eq!(s, "short 3 4.0K 100%");
} }
#[test]
fn test_machine_readable_filecount() {
assert_eq!(human_readable_number(1, "count"), "1");
assert_eq!(human_readable_number(1000, "count"), "1000");
assert_eq!(human_readable_number(1024, "count"), "1024");
}
#[test] #[test]
fn test_human_readable_number() { fn test_human_readable_number() {
assert_eq!(human_readable_number(1, ""), "1B"); assert_eq!(human_readable_number(1, ""), "1B");
@@ -601,7 +669,7 @@ mod tests {
size: 2_u64.pow(size), size: 2_u64.pow(size),
children: vec![], children: vec![],
}; };
let first_size_bar = repeat_n(BLOCKS[0], 13).collect(); let first_size_bar = repeat(BLOCKS[0]).take(13).collect();
let dd = DrawData { let dd = DrawData {
indent: "".into(), indent: "".into(),
percent_bar: first_size_bar, percent_bar: first_size_bar,

View File

@@ -1,6 +1,3 @@
use stfu8::encode_u8;
use crate::display::get_printable_name;
use crate::display_node::DisplayNode; use crate::display_node::DisplayNode;
use crate::node::FileTime; use crate::node::FileTime;
use crate::node::Node; use crate::node::Node;
@@ -17,22 +14,23 @@ pub struct AggregateData {
pub number_of_lines: usize, pub number_of_lines: usize,
pub depth: usize, pub depth: usize,
pub using_a_filter: bool, pub using_a_filter: bool,
pub short_paths: bool,
} }
pub fn get_biggest( pub fn get_biggest(
top_level_nodes: Vec<Node>, mut top_level_nodes: Vec<Node>,
display_data: AggregateData, display_data: AggregateData,
by_filetime: &Option<FileTime>, by_filetime: &Option<FileTime>,
keep_collapsed: HashSet<PathBuf>, keep_collapsed: HashSet<PathBuf>,
) -> DisplayNode { ) -> Option<DisplayNode> {
if top_level_nodes.is_empty() {
// perhaps change this, bring back Error object?
return None;
}
let mut heap = BinaryHeap::new(); let mut heap = BinaryHeap::new();
let number_top_level_nodes = top_level_nodes.len(); let number_top_level_nodes = top_level_nodes.len();
let root; let root;
if number_top_level_nodes == 0 { if number_top_level_nodes > 1 {
root = total_node_builder(0, vec![])
} else if number_top_level_nodes > 1 {
let size = if by_filetime.is_some() { let size = if by_filetime.is_some() {
top_level_nodes top_level_nodes
.iter() .iter()
@@ -43,25 +41,28 @@ pub fn get_biggest(
top_level_nodes.iter().map(|node| node.size).sum() top_level_nodes.iter().map(|node| node.size).sum()
}; };
let nodes = handle_duplicate_top_level_names(top_level_nodes, display_data.short_paths); root = Node {
root = total_node_builder(size, nodes); name: PathBuf::from("(total)"),
size,
children: top_level_nodes,
inode_device: None,
depth: 0,
};
// Always include the base nodes if we add a 'parent' (total) node
heap = always_add_children(&display_data, &root, heap); heap = always_add_children(&display_data, &root, heap);
} else { } else {
root = top_level_nodes.into_iter().next().unwrap(); root = top_level_nodes.into_iter().next().unwrap();
heap = add_children(&display_data, &root, heap); heap = add_children(&display_data, &root, heap);
} }
fill_remaining_lines(heap, &root, display_data, keep_collapsed) let result = fill_remaining_lines(
} heap,
&root,
fn total_node_builder(size: u64, children: Vec<Node>) -> Node { display_data,
Node { keep_collapsed,
name: PathBuf::from("(total)"), );
size, Some(result)
children,
inode_device: None,
depth: 0,
}
} }
pub fn fill_remaining_lines<'a>( pub fn fill_remaining_lines<'a>(
@@ -76,8 +77,6 @@ pub fn fill_remaining_lines<'a>(
let line = heap.pop(); let line = heap.pop();
match line { match line {
Some(line) => { Some(line) => {
// If we are not doing only_file OR if we are doing
// only_file and it has no children (ie is a file not a dir)
if !display_data.only_file || line.children.is_empty() { if !display_data.only_file || line.children.is_empty() {
allowed_nodes.insert(line.name.as_path(), line); allowed_nodes.insert(line.name.as_path(), line);
} }
@@ -159,63 +158,10 @@ fn flat_rebuilder(allowed_nodes: HashMap<&Path, &Node>, current: &Node) -> Displ
fn build_display_node(mut new_children: Vec<DisplayNode>, current: &Node) -> DisplayNode { fn build_display_node(mut new_children: Vec<DisplayNode>, current: &Node) -> DisplayNode {
new_children.sort_by(|lhs, rhs| lhs.cmp(rhs).reverse()); new_children.sort_by(|lhs, rhs| lhs.cmp(rhs).reverse());
// println!("{:?}", current.name);
DisplayNode { DisplayNode {
name: current.name.clone(), name: PathBuf::from(current.name.display().to_string()),
size: current.size, size: current.size,
children: new_children, children: new_children,
} }
} }
fn names_have_dup(top_level_nodes: &Vec<Node>) -> bool {
let mut stored = HashSet::new();
for node in top_level_nodes {
let name = get_printable_name(&node.name, true);
if stored.contains(&name) {
return true;
}
stored.insert(name);
}
false
}
fn handle_duplicate_top_level_names(top_level_nodes: Vec<Node>, short_paths: bool) -> Vec<Node> {
// If we have top level names that are the same - we need to tweak them:
if short_paths && names_have_dup(&top_level_nodes) {
let mut new_top_nodes = top_level_nodes.clone();
let mut dir_walk_up_count = 0;
while names_have_dup(&new_top_nodes) && dir_walk_up_count < 10 {
dir_walk_up_count += 1;
let mut newer = vec![];
for node in new_top_nodes.iter() {
let mut folders = node.name.iter().rev();
// Get parent folder (if second time round get grandparent and so on)
for _ in 0..dir_walk_up_count {
folders.next();
}
match folders.next() {
// Add (parent_name) to path of Node
Some(data) => {
let parent = encode_u8(data.as_encoded_bytes());
let current_node = node.name.display();
let n = Node {
name: PathBuf::from(format!("{current_node}({parent})")),
size: node.size,
children: node.children.clone(),
inode_device: node.inode_device,
depth: node.depth,
};
newer.push(n)
}
// Node does not have a parent
None => newer.push(node.clone()),
}
}
new_top_nodes = newer;
}
new_top_nodes
} else {
top_level_nodes
}
}

View File

@@ -15,7 +15,7 @@ pub fn get_all_file_types(
top_level_nodes: &[Node], top_level_nodes: &[Node],
n: usize, n: usize,
by_filetime: &Option<FileTime>, by_filetime: &Option<FileTime>,
) -> DisplayNode { ) -> Option<DisplayNode> {
let ext_nodes = { let ext_nodes = {
let mut extension_cumulative_sizes = HashMap::new(); let mut extension_cumulative_sizes = HashMap::new();
build_by_all_file_types(top_level_nodes, &mut extension_cumulative_sizes); build_by_all_file_types(top_level_nodes, &mut extension_cumulative_sizes);
@@ -67,11 +67,13 @@ pub fn get_all_file_types(
displayed.iter().map(|node| node.size).sum() displayed.iter().map(|node| node.size).sum()
}; };
DisplayNode { let result = DisplayNode {
name: PathBuf::from("(total)"), name: PathBuf::from("(total)"),
size: actual_size, size: actual_size,
children: displayed, children: displayed,
} };
Some(result)
} }
fn build_by_all_file_types<'a>( fn build_by_all_file_types<'a>(

View File

@@ -10,11 +10,9 @@ mod platform;
mod progress; mod progress;
mod utils; mod utils;
use crate::cli::Cli; use crate::cli::build_cli;
use crate::config::Config;
use crate::display_node::DisplayNode;
use crate::progress::RuntimeErrors; use crate::progress::RuntimeErrors;
use clap::Parser; use clap::parser::ValuesRef;
use dir_walker::WalkData; use dir_walker::WalkData;
use display::InitialDisplayData; use display::InitialDisplayData;
use filter::AggregateData; use filter::AggregateData;
@@ -22,15 +20,15 @@ use progress::PIndicator;
use regex::Error; use regex::Error;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::fs::{read, read_to_string}; use std::fs::read_to_string;
use std::io; use std::io;
use std::io::Read;
use std::panic; use std::panic;
use std::process; use std::process;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use sysinfo::System; use sysinfo::{System, SystemExt};
use utils::canonicalize_absolute_path;
use self::display::draw_it; use self::display::draw_it;
use config::get_config; use config::get_config;
@@ -41,7 +39,7 @@ use filter_type::get_all_file_types;
use regex::Regex; use regex::Regex;
use std::cmp::max; use std::cmp::max;
use std::path::PathBuf; use std::path::PathBuf;
use terminal_size::{Height, Width, terminal_size}; use terminal_size::{terminal_size, Height, Width};
use utils::get_filesystem_devices; use utils::get_filesystem_devices;
use utils::simplify_dir_names; use utils::simplify_dir_names;
@@ -101,10 +99,9 @@ fn get_width_of_terminal() -> usize {
.unwrap_or(DEFAULT_TERMINAL_WIDTH) .unwrap_or(DEFAULT_TERMINAL_WIDTH)
} }
fn get_regex_value(maybe_value: Option<&Vec<String>>) -> Vec<Regex> { fn get_regex_value(maybe_value: Option<ValuesRef<String>>) -> Vec<Regex> {
maybe_value maybe_value
.unwrap_or(&Vec::new()) .unwrap_or_default()
.iter()
.map(|reg| { .map(|reg| {
Regex::new(reg).unwrap_or_else(|err| { Regex::new(reg).unwrap_or_else(|err| {
eprintln!("Ignoring bad value for regex {err:?}"); eprintln!("Ignoring bad value for regex {err:?}");
@@ -115,37 +112,64 @@ fn get_regex_value(maybe_value: Option<&Vec<String>>) -> Vec<Regex> {
} }
fn main() { fn main() {
let options = Cli::parse(); let options = build_cli().get_matches();
let config = get_config(options.config.as_ref()); let config = get_config(options.get_one::<String>("config").cloned());
let errors = RuntimeErrors::default(); let errors = RuntimeErrors::default();
let error_listen_for_ctrlc = Arc::new(Mutex::new(errors)); let error_listen_for_ctrlc = Arc::new(Mutex::new(errors));
let errors_for_rayon = error_listen_for_ctrlc.clone(); let errors_for_rayon = error_listen_for_ctrlc.clone();
let errors_final = error_listen_for_ctrlc.clone();
let is_in_listing = Arc::new(AtomicBool::new(false));
let cloned_is_in_listing = Arc::clone(&is_in_listing);
ctrlc::set_handler(move || { ctrlc::set_handler(move || {
error_listen_for_ctrlc.lock().unwrap().abort = true;
println!("\nAborting"); println!("\nAborting");
process::exit(1); if cloned_is_in_listing.load(Ordering::Relaxed) {
process::exit(1);
}
}) })
.expect("Error setting Ctrl-C handler"); .expect("Error setting Ctrl-C handler");
let target_dirs = if let Some(path) = config.get_files0_from(&options) { is_in_listing.store(true, Ordering::Relaxed);
read_paths_from_source(&path, true) let target_dirs = match config.get_files_from(&options) {
} else if let Some(path) = config.get_files_from(&options) { Some(path) => {
read_paths_from_source(&path, false) if path == "-" {
} else { let mut targets_to_add = io::stdin()
match options.params { .lines()
Some(ref values) => values.clone(), .map_while(Result::ok)
None => vec![".".to_owned()], .collect::<Vec<String>>();
if targets_to_add.is_empty() {
eprintln!("No input provided, defaulting to current directory");
targets_to_add.push(".".to_owned());
}
targets_to_add
} else {
// read file
match read_to_string(path) {
Ok(file_content) => file_content.lines().map(|x| x.to_string()).collect(),
Err(e) => {
eprintln!("Error reading file: {e}");
vec![".".to_owned()]
}
}
}
} }
None => match options.get_many::<String>("params") {
Some(values) => values.cloned().collect(),
None => vec![".".to_owned()],
},
}; };
is_in_listing.store(false, Ordering::Relaxed);
let summarize_file_types = options.file_types; let summarize_file_types = options.get_flag("types");
let filter_regexs = get_regex_value(options.filter.as_ref()); let filter_regexs = get_regex_value(options.get_many("filter"));
let invert_filter_regexs = get_regex_value(options.invert_filter.as_ref()); let invert_filter_regexs = get_regex_value(options.get_many("invert_filter"));
let terminal_width: usize = match options.terminal_width { let terminal_width: usize = match options.get_one::<usize>("width") {
Some(val) => val, Some(&val) => val,
None => get_width_of_terminal(), None => get_width_of_terminal(),
}; };
@@ -154,8 +178,8 @@ fn main() {
// If depth is set, then we set the default number_of_lines to be max // If depth is set, then we set the default number_of_lines to be max
// instead of screen height // instead of screen height
let number_of_lines = match config.get_number_of_lines(&options) { let number_of_lines = match options.get_one::<usize>("number_of_lines") {
Some(val) => val, Some(&val) => val,
None => { None => {
if depth != usize::MAX { if depth != usize::MAX {
usize::MAX usize::MAX
@@ -170,17 +194,16 @@ fn main() {
config.get_force_colors(&options), config.get_force_colors(&options),
); );
let ignore_directories = match options.ignore_directory { let ignore_directories = match options.get_many::<String>("ignore_directory") {
Some(ref values) => values Some(values) => values
.iter() .map(|v| v.as_str())
.map(PathBuf::from) .map(PathBuf::from)
.map(canonicalize_absolute_path)
.collect::<Vec<PathBuf>>(), .collect::<Vec<PathBuf>>(),
None => vec![], None => vec![],
}; };
let ignore_from_file_result = match options.ignore_all_in_file { let ignore_from_file_result = match options.get_one::<String>("ignore_all_in_file") {
Some(ref val) => read_to_string(val) Some(val) => read_to_string(val)
.unwrap() .unwrap()
.lines() .lines()
.map(Regex::new) .map(Regex::new)
@@ -197,17 +220,14 @@ fn main() {
.chain(ignore_from_file) .chain(ignore_from_file)
.collect::<Vec<Regex>>(); .collect::<Vec<Regex>>();
let by_filecount = options.filecount; let by_filecount = options.get_flag("by_filecount");
let by_filetime = config.get_filetime(&options); let by_filetime = config.get_filetime(&options);
let limit_filesystem = options.limit_filesystem; let limit_filesystem = options.get_flag("limit_filesystem");
let follow_links = options.dereference_links; let follow_links = options.get_flag("dereference_links");
let allowed_filesystems = if limit_filesystem {
get_filesystem_devices(&target_dirs, follow_links)
} else {
Default::default()
};
let allowed_filesystems = limit_filesystem
.then(|| get_filesystem_devices(&target_dirs, follow_links))
.unwrap_or_default();
let simplified_dirs = simplify_dir_names(&target_dirs); let simplified_dirs = simplify_dir_names(&target_dirs);
let ignored_full_path: HashSet<PathBuf> = ignore_directories let ignored_full_path: HashSet<PathBuf> = ignore_directories
@@ -224,8 +244,8 @@ fn main() {
indicator.spawn(output_format.clone()) indicator.spawn(output_format.clone())
} }
let keep_collapsed: HashSet<PathBuf> = match options.collapse { let keep_collapsed: HashSet<PathBuf> = match options.get_many::<String>("collapse") {
Some(ref collapse) => { Some(collapse) => {
let mut combined_dirs = HashSet::new(); let mut combined_dirs = HashSet::new();
for collapse_dir in collapse { for collapse_dir in collapse {
for target_dir in target_dirs.iter() { for target_dir in target_dirs.iter() {
@@ -257,98 +277,35 @@ fn main() {
progress_data: indicator.data.clone(), progress_data: indicator.data.clone(),
errors: errors_for_rayon, errors: errors_for_rayon,
}; };
let threads_to_use = config.get_threads(&options); let threads_to_use = config.get_threads(&options);
let stack_size = config.get_custom_stack_size(&options); let stack_size = config.get_custom_stack_size(&options);
init_rayon(&stack_size, &threads_to_use);
init_rayon(&stack_size, &threads_to_use).install(|| { let top_level_nodes = walk_it(simplified_dirs, &walk_data);
let top_level_nodes = walk_it(simplified_dirs, &walk_data);
let tree = match summarize_file_types { let tree = match summarize_file_types {
true => get_all_file_types(&top_level_nodes, number_of_lines, walk_data.by_filetime), true => get_all_file_types(&top_level_nodes, number_of_lines, &by_filetime),
false => { false => {
let agg_data = AggregateData { let agg_data = AggregateData {
min_size: config.get_min_size(&options), min_size: config.get_min_size(&options),
only_dir: config.get_only_dir(&options), only_dir: config.get_only_dir(&options),
only_file: config.get_only_file(&options), only_file: config.get_only_file(&options),
number_of_lines, number_of_lines,
depth, depth,
using_a_filter: !filter_regexs.is_empty() || !invert_filter_regexs.is_empty(), using_a_filter: !filter_regexs.is_empty() || !invert_filter_regexs.is_empty(),
short_paths: !config.get_full_paths(&options), };
}; get_biggest(top_level_nodes, agg_data, &by_filetime, keep_collapsed)
get_biggest(
top_level_nodes,
agg_data,
walk_data.by_filetime,
keep_collapsed,
)
}
};
// Must have stopped indicator before we print to stderr
indicator.stop();
let print_errors = config.get_print_errors(&options);
let final_errors = walk_data.errors.lock().unwrap();
print_any_errors(print_errors, &final_errors);
if tree.children.is_empty() && !final_errors.file_not_found.is_empty() {
std::process::exit(1)
} else {
print_output(
config,
options,
tree,
walk_data.by_filecount,
is_colors,
terminal_width,
)
} }
}); };
}
fn print_output( // Must have stopped indicator before we print to stderr
config: Config, indicator.stop();
options: Cli,
tree: DisplayNode,
by_filecount: bool,
is_colors: bool,
terminal_width: usize,
) {
let output_format = config.get_output_format(&options);
if config.get_output_json(&options) { if errors_final.lock().unwrap().abort {
OUTPUT_TYPE.with(|wrapped| { return;
if by_filecount {
wrapped.replace("count".to_string());
} else {
wrapped.replace(output_format);
}
});
println!("{}", serde_json::to_string(&tree).unwrap());
} else {
let idd = InitialDisplayData {
short_paths: !config.get_full_paths(&options),
is_reversed: !config.get_reverse(&options),
colors_on: is_colors,
by_filecount,
by_filetime: config.get_filetime(&options),
is_screen_reader: config.get_screen_reader(&options),
output_format,
bars_on_right: config.get_bars_on_right(&options),
};
draw_it(
idd,
&tree,
config.get_no_bars(&options),
terminal_width,
config.get_skip_total(&options),
)
} }
}
fn print_any_errors(print_errors: bool, final_errors: &RuntimeErrors) { let final_errors = walk_data.errors.lock().unwrap();
if !final_errors.file_not_found.is_empty() { if !final_errors.file_not_found.is_empty() {
let err = final_errors let err = final_errors
.file_not_found .file_not_found
@@ -356,17 +313,17 @@ fn print_any_errors(print_errors: bool, final_errors: &RuntimeErrors) {
.map(|a| a.as_ref()) .map(|a| a.as_ref())
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join(", "); .join(", ");
eprintln!("No such file or directory: {err}"); eprintln!("No such file or directory: {}", err);
} }
if !final_errors.no_permissions.is_empty() { if !final_errors.no_permissions.is_empty() {
if print_errors { if config.get_print_errors(&options) {
let err = final_errors let err = final_errors
.no_permissions .no_permissions
.iter() .iter()
.map(|a| a.as_ref()) .map(|a| a.as_ref())
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join(", "); .join(", ");
eprintln!("Did not have permissions for directories: {err}"); eprintln!("Did not have permissions for directories: {}", err);
} else { } else {
eprintln!( eprintln!(
"Did not have permissions for all directories (add --print-errors to see errors)" "Did not have permissions for all directories (add --print-errors to see errors)"
@@ -380,105 +337,76 @@ fn print_any_errors(print_errors: bool, final_errors: &RuntimeErrors) {
.map(|a| a.as_ref()) .map(|a| a.as_ref())
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join(", "); .join(", ");
eprintln!("Unknown Error: {err}"); eprintln!("Unknown Error: {}", err);
} }
}
fn read_paths_from_source(path: &str, null_terminated: bool) -> Vec<String> { if let Some(root_node) = tree {
let from_stdin = path == "-"; if config.get_output_json(&options) {
OUTPUT_TYPE.with(|wrapped| {
let result: Result<Vec<String>, Option<String>> = (|| { wrapped.replace(output_format);
// 1) read bytes });
let bytes = if from_stdin { println!("{}", serde_json::to_string(&root_node).unwrap());
let mut b = Vec::new();
io::stdin().lock().read_to_end(&mut b).map_err(|_| None)?;
b
} else { } else {
read(path).map_err(|e| Some(e.to_string()))? let idd = InitialDisplayData {
}; short_paths: !config.get_full_paths(&options),
is_reversed: !config.get_reverse(&options),
colors_on: is_colors,
by_filecount,
by_filetime,
is_screen_reader: config.get_screen_reader(&options),
output_format,
bars_on_right: config.get_bars_on_right(&options),
};
let text = std::str::from_utf8(&bytes).map_err(|e| { draw_it(
if from_stdin { idd,
None config.get_no_bars(&options),
} else { terminal_width,
Some(e.to_string()) &root_node,
} config.get_skip_total(&options),
})?; )
let items: Vec<String> = if null_terminated {
text.split('\0')
.filter(|s| !s.is_empty())
.map(str::to_owned)
.collect()
} else {
text.lines().map(str::to_owned).collect()
};
if from_stdin && items.is_empty() {
return Err(None);
}
Ok(items)
})();
match result {
Ok(v) => v,
Err(None) => {
eprintln!("No files provided, defaulting to current directory");
vec![".".to_owned()]
}
Err(Some(msg)) => {
eprintln!("Failed to read file: {msg}");
vec![".".to_owned()]
} }
} }
} }
fn init_rayon(stack: &Option<usize>, threads: &Option<usize>) -> rayon::ThreadPool { fn init_rayon(stack_size: &Option<usize>, threads: &Option<usize>) {
let stack_size = match stack { // Rayon seems to raise this error on 32-bit builds
Some(s) => Some(*s), // The global thread pool has not been initialized.: ThreadPoolBuildError { kind: GlobalPoolAlreadyInitialized }
None => { if cfg!(target_pointer_width = "64") {
// Do not increase the stack size on a 32 bit system, it will fail let result = panic::catch_unwind(|| build_thread_pool(*stack_size, *threads));
if cfg!(target_pointer_width = "32") { if result.is_err() {
None eprintln!("Problem initializing rayon, try: export RAYON_NUM_THREADS=1")
} else {
let large_stack = usize::pow(1024, 3);
let mut sys = System::new_all();
sys.refresh_memory();
// Larger stack size if possible to handle cases with lots of nested directories
let available = sys.available_memory();
if available > (large_stack * threads.unwrap_or(1)).try_into().unwrap() {
Some(large_stack)
} else {
None
}
}
}
};
match build_thread_pool(stack_size, threads) {
Ok(pool) => pool,
Err(err) => {
eprintln!("Problem initializing rayon, try: export RAYON_NUM_THREADS=1");
if stack.is_none() && stack_size.is_some() {
// stack parameter was none, try with default stack size
if let Ok(pool) = build_thread_pool(None, threads) {
eprintln!("WARNING: not using large stack size, got error: {err}");
return pool;
}
}
panic!("{err}");
} }
} }
} }
fn build_thread_pool( fn build_thread_pool(
stack_size: Option<usize>, stack: Option<usize>,
threads: &Option<usize>, threads: Option<usize>,
) -> Result<rayon::ThreadPool, rayon::ThreadPoolBuildError> { ) -> Result<(), rayon::ThreadPoolBuildError> {
let mut pool_builder = rayon::ThreadPoolBuilder::new(); let mut pool = rayon::ThreadPoolBuilder::new();
if let Some(stack_size_param) = stack_size {
pool_builder = pool_builder.stack_size(stack_size_param);
}
if let Some(thread_count) = threads { if let Some(thread_count) = threads {
pool_builder = pool_builder.num_threads(*thread_count); pool = pool.num_threads(thread_count);
} }
pool_builder.build()
let stack_size = match stack {
Some(s) => Some(s),
None => {
let large_stack = usize::pow(1024, 3);
let mut s = System::new();
s.refresh_memory();
// Larger stack size if possible to handle cases with lots of nested directories
let available = s.available_memory();
if available > large_stack.try_into().unwrap() {
Some(large_stack)
} else {
None
}
}
};
if let Some(stack_size_param) = stack_size {
pool = pool.stack_size(stack_size_param);
}
pool.build_global()
} }

View File

@@ -23,16 +23,6 @@ pub enum FileTime {
Changed, Changed,
} }
impl From<crate::cli::FileTime> for FileTime {
fn from(time: crate::cli::FileTime) -> Self {
match time {
crate::cli::FileTime::Modified => Self::Modified,
crate::cli::FileTime::Accessed => Self::Accessed,
crate::cli::FileTime::Changed => Self::Changed,
}
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn build_node( pub fn build_node(
dir: PathBuf, dir: PathBuf,
@@ -58,9 +48,9 @@ pub fn build_node(
|| is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &dir) || is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &dir)
|| by_filecount && !is_file || by_filecount && !is_file
|| [ || [
(&walk_data.filter_modified_time, data.2.0), (&walk_data.filter_modified_time, data.2 .0),
(&walk_data.filter_accessed_time, data.2.1), (&walk_data.filter_accessed_time, data.2 .1),
(&walk_data.filter_changed_time, data.2.2), (&walk_data.filter_changed_time, data.2 .2),
] ]
.iter() .iter()
.any(|(filter_time, actual_time)| { .any(|(filter_time, actual_time)| {
@@ -71,9 +61,9 @@ pub fn build_node(
1 1
} else if by_filetime.is_some() { } else if by_filetime.is_some() {
match by_filetime { match by_filetime {
Some(FileTime::Modified) => data.2.0.unsigned_abs(), Some(FileTime::Modified) => data.2 .0.unsigned_abs(),
Some(FileTime::Accessed) => data.2.1.unsigned_abs(), Some(FileTime::Accessed) => data.2 .1.unsigned_abs(),
Some(FileTime::Changed) => data.2.2.unsigned_abs(), Some(FileTime::Changed) => data.2 .2.unsigned_abs(),
None => unreachable!(), None => unreachable!(),
} }
} else { } else {

View File

@@ -27,32 +27,15 @@ pub fn get_metadata<P: AsRef<Path>>(
}; };
match metadata { match metadata {
Ok(md) => { Ok(md) => {
let file_size = md.len();
if use_apparent_size { if use_apparent_size {
Some(( Some((
file_size, md.len(),
Some((md.ino(), md.dev())), Some((md.ino(), md.dev())),
(md.mtime(), md.atime(), md.ctime()), (md.mtime(), md.atime(), md.ctime()),
)) ))
} else { } else {
// On NTFS mounts, the reported block count can be unexpectedly large.
// To avoid overestimating disk usage, cap the allocated size to what the
// file should occupy based on the file system I/O block size (blksize).
// Related: https://github.com/bootandy/dust/issues/295
let blksize = md.blksize();
let target_size = file_size.div_ceil(blksize) * blksize;
let reported_size = md.blocks() * get_block_size();
// File systems can pre-allocate more space for a file than what would be necessary
let pre_allocation_buffer = blksize * 65536;
let max_size = target_size + pre_allocation_buffer;
let allocated_size = if reported_size > max_size {
target_size
} else {
reported_size
};
Some(( Some((
allocated_size, md.blocks() * get_block_size(),
Some((md.ino(), md.dev())), Some((md.ino(), md.dev())),
(md.mtime(), md.atime(), md.ctime()), (md.mtime(), md.atime(), md.ctime()),
)) ))

View File

@@ -3,9 +3,9 @@ use std::{
io::Write, io::Write,
path::Path, path::Path,
sync::{ sync::{
Arc, RwLock,
atomic::{AtomicU8, AtomicUsize, Ordering}, atomic::{AtomicU8, AtomicUsize, Ordering},
mpsc::{self, RecvTimeoutError, Sender}, mpsc::{self, RecvTimeoutError, Sender},
Arc, RwLock,
}, },
thread::JoinHandle, thread::JoinHandle,
time::Duration, time::Duration,
@@ -79,6 +79,7 @@ pub struct RuntimeErrors {
pub file_not_found: HashSet<String>, pub file_not_found: HashSet<String>,
pub unknown_error: HashSet<String>, pub unknown_error: HashSet<String>,
pub interrupted_error: i32, pub interrupted_error: i32,
pub abort: bool,
} }
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@@ -118,7 +119,7 @@ impl PIndicator {
let time_info_thread = std::thread::spawn(move || { let time_info_thread = std::thread::spawn(move || {
let mut progress_char_i: usize = 0; let mut progress_char_i: usize = 0;
let mut stderr = std::io::stderr(); let mut stdout = std::io::stdout();
let mut msg = "".to_string(); let mut msg = "".to_string();
// While the timeout triggers we go round the loop // While the timeout triggers we go round the loop
@@ -127,8 +128,7 @@ impl PIndicator {
receiver.recv_timeout(Duration::from_millis(SPINNER_SLEEP_TIME)) receiver.recv_timeout(Duration::from_millis(SPINNER_SLEEP_TIME))
{ {
// Clear the text written by 'write!'& Return at the start of line // Clear the text written by 'write!'& Return at the start of line
let clear = format!("\r{:width$}", " ", width = msg.len()); print!("\r{:width$}", " ", width = msg.len());
write!(stderr, "{clear}").unwrap();
let prog_char = PROGRESS_CHARS[progress_char_i]; let prog_char = PROGRESS_CHARS[progress_char_i];
msg = match data.state.load(ORDERING) { msg = match data.state.load(ORDERING) {
@@ -137,17 +137,15 @@ impl PIndicator {
_ => panic!("Unknown State"), _ => panic!("Unknown State"),
}; };
write!(stderr, "\r{msg}").unwrap(); write!(stdout, "\r{msg}").unwrap();
stderr.flush().unwrap(); stdout.flush().unwrap();
progress_char_i += 1; progress_char_i += 1;
progress_char_i %= PROGRESS_CHARS_LEN; progress_char_i %= PROGRESS_CHARS_LEN;
} }
print!("\r{:width$}", " ", width = msg.len());
let clear = format!("\r{:width$}", " ", width = msg.len()); print!("\r");
write!(stderr, "{clear}").unwrap(); stdout.flush().unwrap();
write!(stderr, "\r").unwrap();
stderr.flush().unwrap();
}); });
self.thread = Some((stop_handler, time_info_thread)) self.thread = Some((stop_handler, time_info_thread))
} }

View File

@@ -67,17 +67,6 @@ pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
path.as_ref().components().collect() path.as_ref().components().collect()
} }
// Canonicalize the path only if it is an absolute path
pub fn canonicalize_absolute_path(path: PathBuf) -> PathBuf {
if !path.is_absolute() {
return path;
}
match std::fs::canonicalize(&path) {
Ok(canonicalized_path) => canonicalized_path,
Err(_) => path,
}
}
pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool { pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool {
if filter_regex.is_empty() { if filter_regex.is_empty() {
false false

View File

@@ -1,2 +0,0 @@
tests/test_dir_files_from/a_file
tests/test_dir_files_from/hello_file

View File

@@ -1 +0,0 @@
hello

View File

@@ -1,4 +1,4 @@
use assert_cmd::{Command, cargo_bin_cmd}; use assert_cmd::Command;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::process::Output; use std::process::Output;
use std::sync::Once; use std::sync::Once;
@@ -61,11 +61,9 @@ fn initialize() {
fn run_cmd<T: AsRef<OsStr>>(command_args: &[T]) -> Output { fn run_cmd<T: AsRef<OsStr>>(command_args: &[T]) -> Output {
initialize(); initialize();
let mut to_run = cargo_bin_cmd!("dust"); let mut to_run = &mut Command::cargo_bin("dust").unwrap();
// Hide progress bar
to_run.arg("-P");
for p in command_args { for p in command_args {
to_run.arg(p); to_run = to_run.arg(p);
} }
to_run.unwrap() to_run.unwrap()
} }

View File

@@ -1,4 +1,4 @@
use assert_cmd::cargo_bin_cmd; use assert_cmd::Command;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::str; use std::str;
@@ -9,16 +9,14 @@ use std::str;
*/ */
fn build_command<T: AsRef<OsStr>>(command_args: Vec<T>) -> String { fn build_command<T: AsRef<OsStr>>(command_args: Vec<T>) -> String {
let mut cmd = cargo_bin_cmd!("dust"); let mut cmd = &mut Command::cargo_bin("dust").unwrap();
// Hide progress bar
cmd.arg("-P");
for p in command_args { for p in command_args {
cmd.arg(p); cmd = cmd.arg(p);
} }
let finished = &cmd.unwrap(); let finished = &cmd.unwrap();
assert_eq!(str::from_utf8(&finished.stderr).unwrap(), ""); let stderr = str::from_utf8(&finished.stderr).unwrap();
assert_eq!(stderr, "");
str::from_utf8(&finished.stdout).unwrap().into() str::from_utf8(&finished.stdout).unwrap().into()
} }
@@ -61,14 +59,6 @@ pub fn test_d_flag_works() {
assert!(!output.contains("hello_file")); assert!(!output.contains("hello_file"));
} }
#[test]
pub fn test_d0_works_on_multiple() {
// We should see the top level directory but not the sub dirs / files:
let output = build_command(vec!["-d", "0", "tests/test_dir/", "tests/test_dir2"]);
assert!(output.contains("test_dir "));
assert!(output.contains("test_dir2"));
}
#[test] #[test]
pub fn test_threads_flag_works() { pub fn test_threads_flag_works() {
let output = build_command(vec!["-T", "1", "tests/test_dir/"]); let output = build_command(vec!["-T", "1", "tests/test_dir/"]);
@@ -103,60 +93,10 @@ pub fn test_ignore_all_in_file() {
assert!(!output.contains(".secret")); assert!(!output.contains(".secret"));
} }
#[test]
pub fn test_files_from_flag_file() {
let output = build_command(vec![
"--files-from",
"tests/test_dir_files_from/files_from.txt",
]);
assert!(output.contains("a_file"));
assert!(output.contains("hello_file"));
}
#[test]
pub fn test_files0_from_flag_file() {
let output = build_command(vec![
"--files0-from",
"tests/test_dir_files_from/files0_from.txt",
]);
assert!(output.contains("a_file"));
assert!(output.contains("hello_file"));
}
#[test]
pub fn test_files_from_flag_stdin() {
let mut cmd = cargo_bin_cmd!("dust");
cmd.arg("-P").arg("--files-from").arg("-");
let input = b"tests/test_dir_files_from/a_file\ntests/test_dir_files_from/hello_file\n";
cmd.write_stdin(input.as_ref());
let finished = &cmd.unwrap();
let stderr = std::str::from_utf8(&finished.stderr).unwrap();
assert_eq!(stderr, "");
let output = std::str::from_utf8(&finished.stdout).unwrap();
assert!(output.contains("a_file"));
assert!(output.contains("hello_file"));
}
#[test]
pub fn test_files0_from_flag_stdin() {
let mut cmd = cargo_bin_cmd!("dust");
cmd.arg("-P").arg("--files0-from").arg("-");
let input = b"tests/test_dir_files_from/a_file\0tests/test_dir_files_from/hello_file\0";
cmd.write_stdin(input.as_ref());
let finished = &cmd.unwrap();
let stderr = std::str::from_utf8(&finished.stderr).unwrap();
assert_eq!(stderr, "");
let output = std::str::from_utf8(&finished.stdout).unwrap();
assert!(output.contains("a_file"));
assert!(output.contains("hello_file"));
}
#[test] #[test]
pub fn test_with_bad_param() { pub fn test_with_bad_param() {
let mut cmd = cargo_bin_cmd!("dust"); let mut cmd = Command::cargo_bin("dust").unwrap();
cmd.arg("-P").arg("bad_place"); let result = cmd.arg("bad_place").unwrap();
let output_error = cmd.unwrap_err();
let result = output_error.as_output().unwrap();
let stderr = str::from_utf8(&result.stderr).unwrap(); let stderr = str::from_utf8(&result.stderr).unwrap();
assert!(stderr.contains("No such file or directory")); assert!(stderr.contains("No such file or directory"));
} }
@@ -321,19 +261,3 @@ pub fn test_collapse() {
assert!(output.contains("many")); assert!(output.contains("many"));
assert!(!output.contains("hello_file")); assert!(!output.contains("hello_file"));
} }
#[test]
pub fn test_handle_duplicate_names() {
// Check that even if we run on a multiple directories with the same name
// we still show the distinct parent dir in the output
let output = build_command(vec![
"tests/test_dir_matching/dave/dup_name",
"tests/test_dir_matching/andy/dup_name",
"ci",
]);
assert!(output.contains("andy"));
assert!(output.contains("dave"));
assert!(output.contains("ci"));
assert!(output.contains("dup_name"));
assert!(!output.contains("test_dir_matching"));
}

View File

@@ -1,4 +1,4 @@
use assert_cmd::{Command, cargo_bin_cmd}; use assert_cmd::Command;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
@@ -44,7 +44,7 @@ pub fn test_soft_sym_link() {
let b = format!(" ┌── {}", file_path_s); let b = format!(" ┌── {}", file_path_s);
let a = format!("─┴ {}", dir_s); let a = format!("─┴ {}", dir_s);
let mut cmd = cargo_bin_cmd!("dust"); let mut cmd = Command::cargo_bin("dust").unwrap();
// Mac test runners create long filenames in tmp directories // Mac test runners create long filenames in tmp directories
let output = cmd let output = cmd
.args(["-p", "-c", "-s", "-w", "999", dir_s]) .args(["-p", "-c", "-s", "-w", "999", dir_s])
@@ -72,7 +72,7 @@ pub fn test_hard_sym_link() {
let file_output = format!(" ┌── {}", file_path_s); let file_output = format!(" ┌── {}", file_path_s);
let dirs_output = format!("─┴ {}", dir_s); let dirs_output = format!("─┴ {}", dir_s);
let mut cmd = cargo_bin_cmd!("dust"); let mut cmd = Command::cargo_bin("dust").unwrap();
// Mac test runners create long filenames in tmp directories // 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;
@@ -96,7 +96,7 @@ pub fn test_hard_sym_link_no_dup_multi_arg() {
let link_name = dir_link.path().join("the_link"); let link_name = dir_link.path().join("the_link");
let link_name_s = link_it(link_name, file_path_s, false); let link_name_s = link_it(link_name, file_path_s, false);
let mut cmd = cargo_bin_cmd!("dust"); let mut cmd = Command::cargo_bin("dust").unwrap();
// Mac test runners create long filenames in tmp directories // Mac test runners create long filenames in tmp directories
let output = cmd let output = cmd
@@ -123,7 +123,7 @@ pub fn test_recursive_sym_link() {
let a = format!("─┬ {}", dir_s); let a = format!("─┬ {}", dir_s);
let b = format!(" └── {}", link_name_s); let b = format!(" └── {}", link_name_s);
let mut cmd = cargo_bin_cmd!("dust"); let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd let output = cmd
.arg("-p") .arg("-p")
.arg("-c") .arg("-c")