Compare commits

...

31 Commits

Author SHA1 Message Date
andy.boot
37a488ed78 refactor: minimum-size & output-format
share code for handling kb/kib/mb/mib logic
2024-03-14 20:15:53 +00:00
andy.boot
5b4c1903fb refactor: minimum-size
Change so it ignores the 'si' flag of output. But allow it to work with
kib/kb/mib/mb etc
2024-02-25 10:59:58 +00:00
andy.boot
96cd5251f9 refactor: merge --si and --display-kb
add new option: --output-format this controls how the output is
summarised and takes:

 nothing = default behaviour
 si = SI units (same as old --si flag)
 b = bytes
 kb = kb
 kib = si kb
 mb = mb
 mib = si mb
....etc
2024-02-25 09:29:13 +00:00
zhaotao1
c8c2a523e8 feat: display the size of a file or directory in "kilobytes"
feat: display the size of a file or directory in "kilobytes"
2024-02-25 09:24:23 +00:00
pandaninjas
a4b5d8573b feat(win): use size on disk for apparent size for OneDrive files (#370)
* feat(win): use size on disk for apparent size for OneDrive files

* apply changes from code review

* run cargo fmt
2024-02-24 15:05:11 +00:00
andy.boot
4a2778b6ea dependency: cargo update 2024-02-21 23:42:30 +00:00
andy.boot
7ee744207b tests: fix test test_apparent_size
my 'many' line is: 4.0K   ┌─┴ many        │ the characters 4.0K are 4 chars.

photosheep's space for 'many' is '44B' which is 3 chars and so my system
needs 1 more char. Hence there is one more space character padding
floating round.
2024-01-29 22:41:42 +00:00
andy.boot
96068518f6 tests: Print to stderr when test fails 2024-01-29 22:41:42 +00:00
andy.boot
10168e0a47 cargo update 2024-01-29 22:41:42 +00:00
andy.boot
6768df9a7b Increment version 2024-01-09 23:16:16 +00:00
andy.boot
e80892a9e7 feat: better error messages
Provide "No such file or directory" error if file is not found.
Provide "Unknown Error" if other error found

Should reduce confusion from the generic other error
2024-01-09 23:02:01 +00:00
andy.boot
cd53fc7494 feat: new param: ignore-all-in-file
Add new cmd line param to ignore all files or directories listed in this
file
2024-01-03 22:24:23 +00:00
andy.boot
e8c7990a17 feat: Custom stack size
If a user reports a crash from stacksize - we can try different stack
memory sizes to try and figure out the cause.

Do not try and initialise rayon on 32 bit builds

Fix rayon initialisation, may not have been called before due to badly
formed closure
2024-01-03 21:46:05 +00:00
andy.boot
c8b61d2f46 dependency: upgrade to clap 4 2023-12-11 23:07:54 +00:00
andy.boot
6e0505bfd7 feat: Reverse direction of bars
Add bars on right option

Add tests
2023-11-23 22:34:24 +00:00
Ylarod
24bdbf036e Rename back to README.md 2023-11-23 22:29:16 +00:00
andy.boot
29085686e1 docs: update README.md
Add note about config file. 

Pre-emptively include the new --bars-on-right flag.
2023-11-21 22:03:00 +00:00
andy.boot
8b1632dde8 docs: Fix filecount docs
We count files not files + folders
2023-11-21 21:24:43 +00:00
andy.boot
f3275cd59c README.md: Add link on build button 2023-11-09 23:31:37 +00:00
andy.boot
939ed89ebb typo: README.md 2023-11-09 23:27:42 +00:00
andy.boot
a58e5f48f6 fix: README.md
Fix link to CI build.
2023-11-09 23:27:23 +00:00
andy.boot
3f9014d8c7 feat: No progress bars if stdout redirected
Progress bars pollute the output if it has been redirected to a file.

If stdout is redirected do not print progress bars
2023-11-09 23:23:05 +00:00
andy.boot
7c54d41ace dependency: remove atty
Read input from clap
2023-11-09 23:23:05 +00:00
andy.boot
2fa14ca19c cargo update: update dependencies (#341) 2023-11-06 23:21:13 +00:00
simonsan
211d89e634 Add scoop install for Windows (#337) 2023-11-06 22:17:44 +00:00
wickles
0038cb24b4 Fix filename completions for zsh and bash (#331) 2023-07-14 18:32:21 +01:00
andy.boot
658f8d2e2b ci: Add musl arm build
ci: Add musl arm build
2023-05-07 11:50:49 +01:00
andy.boot
2c23336794 Increment version
Request for security fix
https://github.com/bootandy/dust/issues/324
2023-05-05 20:40:52 +01:00
andy.boot
a4ae013459 cargo update: update dependencies 2023-05-05 20:40:52 +01:00
JCallicoat
c259d3b566 Add support for cargo-binstall to Cargo.toml
Add support for cargo-binstall to find github release builds.
2023-04-02 20:42:26 +01:00
andy.boot
bdfd3c01a5 cargo update: update dependencies 2023-03-14 20:59:31 +00:00
21 changed files with 1069 additions and 655 deletions

View File

@@ -96,6 +96,11 @@ jobs:
target: arm-unknown-linux-gnueabihf,
use-cross: use-cross,
}
- {
os: ubuntu-latest,
target: arm-unknown-linux-musleabi,
use-cross: use-cross,
}
- {
os: ubuntu-latest,
target: i686-unknown-linux-gnu,
@@ -205,7 +210,9 @@ jobs:
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
# * strip executable?
STRIP="strip" ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;; aarch64-unknown-linux-musl) STRIP="" ;;esac;
STRIP="strip" ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;; aarch64-unknown-linux-musl) STRIP="" ;; armv7-unknown-linux-musleabi) STRIP="" ;; arm-unknown-linux-musleabi) STRIP="" ;; esac;
echo set-output name=STRIP::${STRIP}
echo ::set-output name=STRIP::${STRIP}
- name: Create all needed build/work directories

649
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package]
name = "du-dust"
description = "A more intuitive version of du"
version = "0.8.5"
version = "0.9.0"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2021"
readme = "README.md"
@@ -28,8 +28,7 @@ strip = true
[dependencies]
ansi_term = "0.12"
atty = "0.2.14"
clap = "3.2.17"
clap = "4.4"
lscolors = "0.13"
terminal_size = "0.2"
unicode-width = "0.1"
@@ -44,20 +43,25 @@ sysinfo = "0.27"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"
filesize = "0.2.0"
[dev-dependencies]
assert_cmd = "2"
tempfile = "=3"
[build-dependencies]
clap = "3.2.17"
clap_complete = "3.2.4"
clap_mangen = "0.1"
clap = "4.4"
clap_complete = "4.4"
clap_mangen = "0.2"
[[test]]
name = "integration"
path = "tests/tests.rs"
[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/dust-v{ version }-{ target }{ archive-suffix }"
bin-dir = "dust-v{ version }-{ target }/{ bin }{ binary-ext }"
[package.metadata.deb]
section = "utils"
assets = [

View File

@@ -1,4 +1,5 @@
[![Build Status](https://travis-ci.org/bootandy/dust.svg?branch=master)](https://travis-ci.org/bootandy/dust)
[![Build Status](https://github.com/bootandy/dust/actions/workflows/CICD.yml/badge.svg)](https://github.com/bootandy/dust/actions)
# Dust
@@ -36,6 +37,7 @@ Because I want an easy way to see where my disk is being used.
#### Windows:
- `scoop install dust`
- Windows GNU version - works
- Windows MSVC - requires: [VCRUNTIME140.dll](https://docs.microsoft.com/en-gb/cpp/windows/latest-supported-vc-redist?view=msvc-170)
@@ -66,10 +68,11 @@ Usage: dust -d 3 (Shows 3 levels of subdirectories)
Usage: dust -D (Show only directories (eg dust -D))
Usage: dust -F (Show only files - finds your largest files)
Usage: dust -r (reverse order of output)
Usage: dust -H (si print sizes in powers of 1000 instead of 1024)
Usage: dust -o si/b/kb/kib/mb/mib/gb/gib (si - prints sizes in powers of 1000. Others print size in that format).
Usage: dust -X ignore (ignore all files and directories with the name 'ignore')
Usage: dust -x (Only show directories on the same filesystem)
Usage: dust -b (Do not show percentages or draw ASCII bars)
Usage: dust -B (--bars-on-right - Percent bars moved to right side of screen])
Usage: dust -i (Do not show hidden files)
Usage: dust -c (No colors [monochrome])
Usage: dust -f (Count files instead of diskspace)
@@ -80,9 +83,18 @@ Usage: dust -v regex (Exclude files matching this regex (eg dust -v "\.png$" wou
Usage: dust -L (dereference-links - Treat sym links as directories and go into them)
Usage: dust -P (Disable the progress indicator)
Usage: dust -R (For screen readers. Removes bars/symbols. Adds new column: depth level. (May want to use -p for full path too))
Usage: dust -S (Custom Stack size - Use if you see: 'fatal runtime error: stack overflow' (default allocation: low memory=1048576, high memory=1073741824)"),
Usage: dust --skip-total (No total row will be displayed)
Usage: dust -z 4000000 (Exclude files below size 4MB)
Usage: dust -z 40000/30MB/20kib (Exclude output files/directories below size 40000 bytes / 30MB / 20KiB)
```
## Config file
Dust has a config file where the above options can be set.
Either: `~/.config/dust/config.toml` or `~/.dust.toml`
```
$ cat ~/.config/dust/config.toml
reverse=true
```
## Alternatives

View File

@@ -21,18 +21,20 @@ _dust() {
'--number-of-lines=[Number of lines of output to show. (Default is terminal_height - 10)]: : ' \
'*-X+[Exclude any file or directory with this name]: : ' \
'*--ignore-directory=[Exclude any file or directory with this name]: : ' \
'-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]: : ' \
'--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]: : ' \
'-z+[Minimum size file to include in output]: : ' \
'--min-size=[Minimum size file to include in output]: : ' \
'(-e --filter -t --file_types)*-v+[Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ]: : ' \
'(-e --filter -t --file_types)*--invert-filter=[Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ]: : ' \
'(-t --file_types)*-e+[Only include filepaths matching this regex. For png files type: -e "\\.png$" ]: : ' \
'(-t --file_types)*--filter=[Only include filepaths matching this regex. For png files type: -e "\\.png$" ]: : ' \
'(-e --filter -t --file_types)*-v+[Exclude filepaths matching this regex. To ignore png files type\: -v "\\.png\$" ]: : ' \
'(-e --filter -t --file_types)*--invert-filter=[Exclude filepaths matching this regex. To ignore png files type\: -v "\\.png\$" ]: : ' \
'(-t --file_types)*-e+[Only include filepaths matching this regex. For png files type\: -e "\\.png\$" ]: : ' \
'(-t --file_types)*--filter=[Only include filepaths matching this regex. For png files type\: -e "\\.png\$" ]: : ' \
'-w+[Specify width of output overriding the auto detection of terminal width]: : ' \
'--terminal_width=[Specify width of output overriding the auto detection of terminal width]: : ' \
'-h[Print help information]' \
'--help[Print help information]' \
'-V[Print version information]' \
'--version[Print version information]' \
'-o+[Changes output display size. si will print sizes in powers of 1000. b/bytes kb kib mb mib gb gib will print the whole tree in that size]: : ' \
'--output-format=[Changes output display size. si will print sizes in powers of 1000. b/bytes kb kib mb mib gb gib will print the whole tree in that size]: : ' \
'-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=[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]: : ' \
'-p[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]' \
@@ -43,28 +45,32 @@ _dust() {
'--apparent-size[Use file length instead of blocks]' \
'-r[Print tree upside down (biggest highest)]' \
'--reverse[Print tree upside down (biggest highest)]' \
'-c[No colors will be printed (Useful for commands like: watch)]' \
'--no-colors[No colors will be printed (Useful for commands like: watch)]' \
'-c[No colors will be printed (Useful for commands like\: watch)]' \
'--no-colors[No colors will be printed (Useful for commands like\: watch)]' \
'-b[No percent bars or percentages will be displayed]' \
'--no-percent-bars[No percent bars or percentages will be displayed]' \
'-R[For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)]' \
'--screen-reader[For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)]' \
'-B[percent bars moved to right side of screen]' \
'--bars-on-right[percent bars moved to right side of screen]' \
'-R[For screen readers. Removes bars. Adds new column\: depth level (May want to use -p too for full path)]' \
'--screen-reader[For screen readers. Removes bars. Adds new column\: depth level (May want to use -p too for full path)]' \
'--skip-total[No total row will be displayed]' \
'-f[Directory '\''size'\'' is number of child files/dirs not disk size]' \
'--filecount[Directory '\''size'\'' is number of child files/dirs not 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]' \
'-i[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)--file_types[show only these file types]' \
'-H[print sizes in powers of 1000 (e.g., 1.1G)]' \
'--si[print sizes in powers of 1000 (e.g., 1.1G)]' \
'-P[Disable the progress indication.]' \
'--no-progress[Disable the progress indication.]' \
'(-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.]' \
'(-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)]' \
'*::inputs:' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
'*::params:' \
&& ret=0
}
@@ -74,4 +80,8 @@ _dust_commands() {
_describe -t commands 'dust commands' commands "$@"
}
_dust "$@"
if [ "$funcstack[1]" = "_dust" ]; then
_dust "$@"
else
compdef _dust dust
fi

View File

@@ -25,8 +25,10 @@ Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
[CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Depth to show')
[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, '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 name')
[CompletionResult]::new('-X', 'X ', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this name')
[CompletionResult]::new('--ignore-directory', 'ignore-directory', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this name')
[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('-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('-v', 'v', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
@@ -35,13 +37,13 @@ Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
[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('--terminal_width', 'terminal_width', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Changes output display size. si will print sizes in powers of 1000. b/bytes kb kib mb mib gb gib 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/bytes kb kib mb mib gb gib 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('--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('-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('-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('-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')
@@ -53,23 +55,27 @@ Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
[CompletionResult]::new('--no-colors', 'no-colors', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
[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('-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('-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('-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('--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/dirs not disk size')
[CompletionResult]::new('--filecount', 'filecount', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files/dirs not 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('-i', 'i', [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('--file_types', 'file_types', [CompletionResultType]::ParameterName, 'show only these file types')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'print sizes in powers of 1000 (e.g., 1.1G)')
[CompletionResult]::new('--si', 'si', [CompletionResultType]::ParameterName, 'print sizes in powers of 1000 (e.g., 1.1G)')
[CompletionResult]::new('-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('-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('-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('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
break
}
})

View File

@@ -1,5 +1,5 @@
_dust() {
local i cur prev opts cmds
local i cur prev opts cmd
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
@@ -8,8 +8,8 @@ _dust() {
for i in ${COMP_WORDS[@]}
do
case "${i}" in
"$1")
case "${cmd},${i}" in
",$1")
cmd="dust"
;;
*)
@@ -19,7 +19,7 @@ _dust() {
case "${cmd}" in
dust)
opts="-h -V -d -n -p -X -L -x -s -r -c -b -z -R -f -i -v -e -t -w -H -P -D -F --help --version --depth --number-of-lines --full-paths --ignore-directory --dereference-links --limit-filesystem --apparent-size --reverse --no-colors --no-percent-bars --min-size --screen-reader --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --si --no-progress --only-dir --only-file <inputs>..."
opts="-d -n -p -X -I -L -x -s -r -c -b -B -z -R -f -i -v -e -t -w -P -D -F -o -S -h -V --depth --number-of-lines --full-paths --ignore-directory --ignore-all-in-file --dereference-links --limit-filesystem --apparent-size --reverse --no-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 --only-dir --only-file --output-format --stack-size --help --version [params]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@@ -49,6 +49,14 @@ _dust() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--ignore-all-in-file)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-I)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--min-size)
COMPREPLY=($(compgen -f "${cur}"))
return 0
@@ -81,6 +89,22 @@ _dust() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--output-format)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-o)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--stack-size)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-S)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
@@ -91,4 +115,8 @@ _dust() {
esac
}
complete -F _dust -o bashdefault -o default dust
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
complete -F _dust -o nosort -o bashdefault -o default dust
else
complete -F _dust -o bashdefault -o default dust
fi

View File

@@ -24,6 +24,8 @@ set edit:completion:arg-completer[dust] = {|@words|
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 name'
cand --ignore-directory 'Exclude any file or directory with this name'
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 -z '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$" '
@@ -32,10 +34,10 @@ set edit:completion:arg-completer[dust] = {|@words|
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 --terminal_width 'Specify width of output overriding the auto detection of terminal width'
cand -h 'Print help information'
cand --help 'Print help information'
cand -V 'Print version information'
cand --version 'Print version information'
cand -o 'Changes output display size. si will print sizes in powers of 1000. b/bytes kb kib mb mib gb gib will print the whole tree in that size'
cand --output-format 'Changes output display size. si will print sizes in powers of 1000. b/bytes kb kib mb mib gb gib 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 --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 -p 'Subdirectories will not have their path shortened'
cand --full-paths 'Subdirectories will not have their path shortened'
cand -L 'dereference sym links - Treat sym links as directories and go into them'
@@ -50,23 +52,27 @@ set edit:completion:arg-completer[dust] = {|@words|
cand --no-colors 'No colors will be printed (Useful for commands like: watch)'
cand -b 'No percent bars or percentages will be displayed'
cand --no-percent-bars 'No percent bars or percentages will be displayed'
cand -B 'percent bars moved to right side of screen'
cand --bars-on-right 'percent bars moved to right side of screen'
cand -R 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)'
cand --screen-reader 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)'
cand --skip-total 'No total row will be displayed'
cand -f 'Directory ''size'' is number of child files/dirs not disk size'
cand --filecount 'Directory ''size'' is number of child files/dirs not 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 -i 'Do not display hidden files'
cand --ignore_hidden 'Do not display hidden files'
cand -t 'show only these file types'
cand --file_types 'show only these file types'
cand -H 'print sizes in powers of 1000 (e.g., 1.1G)'
cand --si 'print sizes in powers of 1000 (e.g., 1.1G)'
cand -P 'Disable the progress indication.'
cand --no-progress 'Disable the progress indication.'
cand -D '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 --only-file 'Only files will be displayed. (Finds your largest files)'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
]
$completions[$command]

View File

@@ -1,12 +1,13 @@
complete -c dust -s d -l depth -d 'Depth to show' -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 name' -r
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
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 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 h -l help -d 'Print help information'
complete -c dust -s V -l version -d 'Print version information'
complete -c dust -s o -l output-format -d 'Changes output display size. si will print sizes in powers of 1000. b/bytes kb kib mb mib gb gib will print the whole tree in that size' -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 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 x -l limit-filesystem -d 'Only count the files and directories on the same filesystem as the supplied directory'
@@ -14,12 +15,14 @@ complete -c dust -s s -l apparent-size -d 'Use file length instead of blocks'
complete -c dust -s r -l reverse -d 'Print tree upside down (biggest highest)'
complete -c dust -s c -l no-colors -d 'No colors will be printed (Useful for commands like: watch)'
complete -c dust -s b -l no-percent-bars -d 'No percent bars or percentages will be displayed'
complete -c dust -s B -l bars-on-right -d 'percent bars moved to right side of screen'
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 -s f -l filecount -d 'Directory \'size\' is number of child files/dirs not 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 t -l file_types -d 'show only these file types'
complete -c dust -s H -l si -d 'print sizes in powers of 1000 (e.g., 1.1G)'
complete -c dust -s P -l no-progress -d 'Disable the progress indication.'
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 h -l help -d 'Print help'
complete -c dust -s V -l version -d 'Print version'

View File

@@ -1,20 +1,14 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH Dust 1 "Dust 0.8.5"
.TH Dust 1 "Dust 0.9.0"
.SH NAME
Dust \- Like du but more intuitive
.SH SYNOPSIS
\fBDust\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fB\-d\fR|\fB\-\-depth\fR] [\fB\-n\fR|\fB\-\-number\-of\-lines\fR] [\fB\-p\fR|\fB\-\-full\-paths\fR] [\fB\-X\fR|\fB\-\-ignore\-directory\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\-b\fR|\fB\-\-no\-percent\-bars\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\-H\fR|\fB\-\-si\fR] [\fB\-P\fR|\fB\-\-no\-progress\fR] [\fB\-D\fR|\fB\-\-only\-dir\fR] [\fB\-F\fR|\fB\-\-only\-file\fR] [\fIinputs\fR]
\fBdust\fR [\fB\-d\fR|\fB\-\-depth\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\-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\-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\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIparams\fR]
.SH DESCRIPTION
Like du but more intuitive
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help information
.TP
\fB\-V\fR, \fB\-\-version\fR
Print version information
.TP
\fB\-d\fR, \fB\-\-depth\fR
Depth to show
.TP
@@ -27,6 +21,9 @@ Subdirectories will not have their path shortened
\fB\-X\fR, \fB\-\-ignore\-directory\fR
Exclude any file or directory with this name
.TP
\fB\-I\fR, \fB\-\-ignore\-all\-in\-file\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
.TP
\fB\-L\fR, \fB\-\-dereference\-links\fR
dereference sym links \- Treat sym links as directories and go into them
.TP
@@ -45,6 +42,9 @@ No colors will be printed (Useful for commands like: watch)
\fB\-b\fR, \fB\-\-no\-percent\-bars\fR
No percent bars or percentages will be displayed
.TP
\fB\-B\fR, \fB\-\-bars\-on\-right\fR
percent bars moved to right side of screen
.TP
\fB\-z\fR, \fB\-\-min\-size\fR
Minimum size file to include in output
.TP
@@ -55,7 +55,7 @@ For screen readers. Removes bars. Adds new column: depth level (May want to use
No total row will be displayed
.TP
\fB\-f\fR, \fB\-\-filecount\fR
Directory \*(Aqsize\*(Aq is number of child files/dirs not disk size
Directory \*(Aqsize\*(Aq is number of child files instead of disk size
.TP
\fB\-i\fR, \fB\-\-ignore_hidden\fR
Do not display hidden files
@@ -72,9 +72,6 @@ show only these file types
\fB\-w\fR, \fB\-\-terminal_width\fR
Specify width of output overriding the auto detection of terminal width
.TP
\fB\-H\fR, \fB\-\-si\fR
print sizes in powers of 1000 (e.g., 1.1G)
.TP
\fB\-P\fR, \fB\-\-no\-progress\fR
Disable the progress indication.
.TP
@@ -84,7 +81,19 @@ Only directories will be displayed.
\fB\-F\fR, \fB\-\-only\-file\fR
Only files will be displayed. (Finds your largest files)
.TP
[\fIinputs\fR]
\fB\-o\fR, \fB\-\-output\-format\fR
Changes output display size. si will print sizes in powers of 1000. b/bytes kb kib mb mib gb gib will print the whole tree in that size
.TP
\fB\-S\fR, \fB\-\-stack\-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)
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
\fB\-V\fR, \fB\-\-version\fR
Print version
.TP
[\fIparams\fR]
.SH VERSION
v0.8.5
v0.9.0

View File

@@ -1,9 +1,9 @@
use clap::{Arg, Command};
use clap::{value_parser, Arg, Command};
// For single thread mode set this variable on your command line:
// export RAYON_NUM_THREADS=1
pub fn build_cli() -> Command<'static> {
pub fn build_cli() -> Command {
Command::new("Dust")
.about("Like du but more intuitive")
.version(env!("CARGO_PKG_VERSION"))
@@ -12,105 +12,127 @@ pub fn build_cli() -> Command<'static> {
Arg::new("depth")
.short('d')
.long("depth")
.value_parser(value_parser!(usize))
.help("Depth to show")
.takes_value(true)
.num_args(1)
)
.arg(
Arg::new("number_of_lines")
.short('n')
.long("number-of-lines")
.value_parser(value_parser!(usize))
.help("Number of lines of output to show. (Default is terminal_height - 10)")
.takes_value(true)
.num_args(1)
)
.arg(
Arg::new("display_full_paths")
.short('p')
.long("full-paths")
.action(clap::ArgAction::SetTrue)
.help("Subdirectories will not have their path shortened"),
)
.arg(
Arg::new("ignore_directory")
.short('X')
.long("ignore-directory")
.takes_value(true)
.number_of_values(1)
.multiple_occurrences(true)
.action(clap::ArgAction::Append)
.help("Exclude any file or directory with this name"),
)
.arg(
Arg::new("ignore_all_in_file")
.short('I')
.long("ignore-all-in-file")
.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"),
)
.arg(
Arg::new("dereference_links")
.short('L')
.long("dereference-links")
.action(clap::ArgAction::SetTrue)
.help("dereference sym links - Treat sym links as directories and go into them"),
)
.arg(
Arg::new("limit_filesystem")
.short('x')
.long("limit-filesystem")
.action(clap::ArgAction::SetTrue)
.help("Only count the files and directories on the same filesystem as the supplied directory"),
)
.arg(
Arg::new("display_apparent_size")
.short('s')
.long("apparent-size")
.action(clap::ArgAction::SetTrue)
.help("Use file length instead of blocks"),
)
.arg(
Arg::new("reverse")
.short('r')
.long("reverse")
.action(clap::ArgAction::SetTrue)
.help("Print tree upside down (biggest highest)"),
)
.arg(
Arg::new("no_colors")
.short('c')
.long("no-colors")
.action(clap::ArgAction::SetTrue)
.help("No colors will be printed (Useful for commands like: watch)"),
)
.arg(
Arg::new("no_bars")
.short('b')
.long("no-percent-bars")
.action(clap::ArgAction::SetTrue)
.help("No percent bars or percentages will be displayed"),
)
.arg(
Arg::new("bars_on_right")
.short('B')
.long("bars-on-right")
.action(clap::ArgAction::SetTrue)
.help("percent bars moved to right side of screen"),
)
.arg(
Arg::new("min_size")
.short('z')
.long("min-size")
.takes_value(true)
.number_of_values(1)
.num_args(1)
.help("Minimum size file to include in output"),
)
.arg(
Arg::new("screen_reader")
.short('R')
.long("screen-reader")
.action(clap::ArgAction::SetTrue)
.help("For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)"),
)
.arg(
Arg::new("skip_total")
.long("skip-total")
.action(clap::ArgAction::SetTrue)
.help("No total row will be displayed"),
)
.arg(
Arg::new("by_filecount")
.short('f')
.long("filecount")
.help("Directory 'size' is number of child files/dirs not disk size"),
.action(clap::ArgAction::SetTrue)
.help("Directory 'size' is number of child files instead of disk size"),
)
.arg(
Arg::new("ignore_hidden")
.short('i') // Do not use 'h' this is used by 'help'
.long("ignore_hidden")
.action(clap::ArgAction::SetTrue)
.help("Do not display hidden files"),
)
.arg(
Arg::new("invert_filter")
.short('v')
.long("invert-filter")
.takes_value(true)
.number_of_values(1)
.multiple_occurrences(true)
.action(clap::ArgAction::Append)
.conflicts_with("filter")
.conflicts_with("types")
.help("Exclude filepaths matching this regex. To ignore png files type: -v \"\\.png$\" "),
@@ -119,9 +141,7 @@ pub fn build_cli() -> Command<'static> {
Arg::new("filter")
.short('e')
.long("filter")
.takes_value(true)
.number_of_values(1)
.multiple_occurrences(true)
.action(clap::ArgAction::Append)
.conflicts_with("types")
.help("Only include filepaths matching this regex. For png files type: -e \"\\.png$\" "),
)
@@ -131,26 +151,22 @@ pub fn build_cli() -> Command<'static> {
.long("file_types")
.conflicts_with("depth")
.conflicts_with("only_dir")
.action(clap::ArgAction::SetTrue)
.help("show only these file types"),
)
.arg(
Arg::new("width")
.short('w')
.long("terminal_width")
.takes_value(true)
.number_of_values(1)
.num_args(1)
.value_parser(value_parser!(usize))
.help("Specify width of output overriding the auto detection of terminal width"),
)
.arg(
Arg::new("iso")
.short('H')
.long("si")
.help("print sizes in powers of 1000 (e.g., 1.1G)")
)
.arg(
Arg::new("disable_progress")
.short('P')
.long("no-progress")
.action(clap::ArgAction::SetTrue)
.help("Disable the progress indication."),
)
.arg(
@@ -159,6 +175,7 @@ pub fn build_cli() -> Command<'static> {
.long("only-dir")
.conflicts_with("only_file")
.conflicts_with("types")
.action(clap::ArgAction::SetTrue)
.help("Only directories will be displayed."),
)
.arg(
@@ -166,7 +183,24 @@ pub fn build_cli() -> Command<'static> {
.short('F')
.long("only-file")
.conflicts_with("only_dir")
.action(clap::ArgAction::SetTrue)
.help("Only files will be displayed. (Finds your largest files)"),
)
.arg(Arg::new("inputs").multiple_occurrences(true))
.arg(
Arg::new("output_format")
.short('o')
.long("output-format")
.value_parser(value_parser!(String))
.help("Changes output display size. si will print sizes in powers of 1000. b/bytes kb kib mb mib gb gib will print the whole tree in that size")
)
.arg(
Arg::new("stack_size")
.short('S')
.long("stack-size")
.num_args(1)
.value_parser(value_parser!(usize))
.help("Specify memory to use as stack size - use if you see: 'fatal runtime error: stack overflow' (default low memory=1048576, high memory=1073741824)"),
)
.arg(Arg::new("params").num_args(1..)
.value_parser(value_parser!(String)))
}

View File

@@ -1,10 +1,12 @@
use clap::ArgMatches;
use config_file::FromConfigFile;
use regex::Regex;
use serde::Deserialize;
use std::io::IsTerminal;
use std::path::Path;
use std::path::PathBuf;
use crate::display::UNITS;
use crate::display::get_number_format;
#[derive(Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
@@ -18,110 +20,129 @@ pub struct Config {
pub skip_total: Option<bool>,
pub screen_reader: Option<bool>,
pub ignore_hidden: Option<bool>,
pub iso: Option<bool>,
pub output_format: Option<String>,
pub min_size: Option<String>,
pub only_dir: Option<bool>,
pub only_file: Option<bool>,
pub disable_progress: Option<bool>,
pub depth: Option<usize>,
pub bars_on_right: Option<bool>,
pub stack_size: Option<usize>,
}
impl Config {
pub fn get_no_colors(&self, options: &ArgMatches) -> bool {
Some(true) == self.no_colors || options.is_present("no_colors")
Some(true) == self.no_colors || options.get_flag("no_colors")
}
pub fn get_disable_progress(&self, options: &ArgMatches) -> bool {
Some(true) == self.disable_progress || options.is_present("disable_progress")
Some(true) == self.disable_progress
|| options.get_flag("disable_progress")
|| !std::io::stdout().is_terminal()
}
pub fn get_apparent_size(&self, options: &ArgMatches) -> bool {
Some(true) == self.display_apparent_size || options.is_present("display_apparent_size")
Some(true) == self.display_apparent_size || options.get_flag("display_apparent_size")
}
pub fn get_ignore_hidden(&self, options: &ArgMatches) -> bool {
Some(true) == self.ignore_hidden || options.is_present("ignore_hidden")
Some(true) == self.ignore_hidden || options.get_flag("ignore_hidden")
}
pub fn get_full_paths(&self, options: &ArgMatches) -> bool {
// If we are only showing files, always show full paths
Some(true) == self.display_full_paths
|| options.is_present("display_full_paths")
|| options.get_flag("display_full_paths")
|| self.get_only_file(options)
}
pub fn get_reverse(&self, options: &ArgMatches) -> bool {
Some(true) == self.reverse || options.is_present("reverse")
Some(true) == self.reverse || options.get_flag("reverse")
}
pub fn get_no_bars(&self, options: &ArgMatches) -> bool {
Some(true) == self.no_bars || options.is_present("no_bars")
Some(true) == self.no_bars || options.get_flag("no_bars")
}
pub fn get_iso(&self, options: &ArgMatches) -> bool {
Some(true) == self.iso || options.is_present("iso")
pub fn get_output_format(&self, options: &ArgMatches) -> String {
let out_fmt = options.get_one::<String>("output_format");
(match out_fmt {
None => match &self.output_format {
None => "".to_string(),
Some(x) => x.to_string(),
},
Some(x) => x.into(),
})
.to_lowercase()
}
pub fn get_skip_total(&self, options: &ArgMatches) -> bool {
Some(true) == self.skip_total || options.is_present("skip_total")
Some(true) == self.skip_total || options.get_flag("skip_total")
}
pub fn get_screen_reader(&self, options: &ArgMatches) -> bool {
Some(true) == self.screen_reader || options.is_present("screen_reader")
Some(true) == self.screen_reader || options.get_flag("screen_reader")
}
pub fn get_depth(&self, options: &ArgMatches) -> usize {
if let Some(v) = options.value_of("depth") {
if let Ok(v) = v.parse::<usize>() {
return v;
}
if let Some(v) = options.get_one::<usize>("depth") {
return *v;
}
self.depth.unwrap_or(usize::MAX)
}
pub fn get_min_size(&self, options: &ArgMatches, iso: bool) -> Option<usize> {
let size_from_param = options.value_of("min_size");
self._get_min_size(size_from_param, iso)
pub fn get_min_size(&self, options: &ArgMatches) -> Option<usize> {
let size_from_param = options.get_one::<String>("min_size");
self._get_min_size(size_from_param)
}
fn _get_min_size(&self, min_size: Option<&str>, iso: bool) -> Option<usize> {
let size_from_param = min_size.and_then(|a| convert_min_size(a, iso));
fn _get_min_size(&self, min_size: Option<&String>) -> Option<usize> {
let size_from_param = min_size.and_then(|a| convert_min_size(a));
if size_from_param.is_none() {
self.min_size
.as_ref()
.and_then(|a| convert_min_size(a.as_ref(), iso))
.and_then(|a| convert_min_size(a.as_ref()))
} else {
size_from_param
}
}
pub fn get_only_dir(&self, options: &ArgMatches) -> bool {
Some(true) == self.only_dir || options.is_present("only_dir")
Some(true) == self.only_dir || options.get_flag("only_dir")
}
pub fn get_only_file(&self, options: &ArgMatches) -> bool {
Some(true) == self.only_file || options.is_present("only_file")
Some(true) == self.only_file || options.get_flag("only_file")
}
pub fn get_bars_on_right(&self, options: &ArgMatches) -> bool {
Some(true) == self.bars_on_right || options.get_flag("bars_on_right")
}
pub fn get_custom_stack_size(&self, options: &ArgMatches) -> Option<usize> {
let from_cmd_line = options.get_one::<usize>("stack_size");
if from_cmd_line.is_none() {
self.stack_size
} else {
from_cmd_line.copied()
}
}
}
fn convert_min_size(input: &str, iso: bool) -> Option<usize> {
let chars_as_vec: Vec<char> = input.chars().collect();
match chars_as_vec.split_last() {
Some((last, start)) => {
let mut starts: String = start.iter().collect::<String>();
fn convert_min_size(input: &str) -> Option<usize> {
let re = Regex::new(r"([0-9]+)(\w*)").unwrap();
for (i, u) in UNITS.iter().rev().enumerate() {
if Some(*u) == last.to_uppercase().next() {
return match starts.parse::<usize>() {
Ok(pure) => {
let num: usize = if iso { 1000 } else { 1024 };
let marker = pure * num.pow((i + 1) as u32);
Some(marker)
}
Err(_) => {
if let Some(cap) = re.captures(input) {
let (_, [digits, letters]) = cap.extract();
// Failure to parse should be impossible due to regex match
let digits_as_usize: Option<usize> = digits.parse().ok();
match digits_as_usize {
Some(parsed_digits) => {
let number_format = get_number_format(&letters.to_lowercase());
match number_format {
Some((multiple, _)) => Some(parsed_digits * (multiple as usize)),
None => {
if letters.eq("") {
Some(parsed_digits)
} else {
eprintln!("Ignoring invalid min-size: {input}");
None
}
};
}
}
}
starts.push(*last);
starts
.parse()
.map_err(|_| {
eprintln!("Ignoring invalid min-size: {input}");
})
.ok()
None => None,
}
None => None,
} else {
None
}
}
@@ -151,17 +172,17 @@ pub fn get_config() -> Config {
mod tests {
#[allow(unused_imports)]
use super::*;
use clap::{Arg, ArgMatches, Command};
use clap::{value_parser, Arg, ArgMatches, Command};
#[test]
fn test_conversion() {
assert_eq!(convert_min_size("55", false), Some(55));
assert_eq!(convert_min_size("12344321", false), Some(12344321));
assert_eq!(convert_min_size("95RUBBISH", false), None);
assert_eq!(convert_min_size("10K", false), Some(10 * 1024));
assert_eq!(convert_min_size("10M", false), Some(10 * 1024usize.pow(2)));
assert_eq!(convert_min_size("10M", true), Some(10 * 1000usize.pow(2)));
assert_eq!(convert_min_size("2G", false), Some(2 * 1024usize.pow(3)));
assert_eq!(convert_min_size("55"), Some(55));
assert_eq!(convert_min_size("12344321"), Some(12344321));
assert_eq!(convert_min_size("95RUBBISH"), None);
assert_eq!(convert_min_size("10K"), Some(10 * 1024));
assert_eq!(convert_min_size("10M"), Some(10 * 1024usize.pow(2)));
assert_eq!(convert_min_size("10MiB"), Some(10 * 1000usize.pow(2)));
assert_eq!(convert_min_size("2G"), Some(2 * 1024usize.pow(3)));
}
#[test]
@@ -170,11 +191,11 @@ mod tests {
min_size: Some("1K".to_owned()),
..Default::default()
};
assert_eq!(c._get_min_size(None, false), Some(1024));
assert_eq!(c._get_min_size(Some("2K"), false), Some(2048));
assert_eq!(c._get_min_size(None), Some(1024));
assert_eq!(c._get_min_size(Some(&"2K".into())), Some(2048));
assert_eq!(c._get_min_size(None, true), Some(1000));
assert_eq!(c._get_min_size(Some("2K"), true), Some(2000));
assert_eq!(c._get_min_size(Some(&"1kib".into())), Some(1000));
assert_eq!(c._get_min_size(Some(&"2KiB".into())), Some(2000));
}
#[test]
@@ -208,8 +229,12 @@ mod tests {
fn get_args(args: Vec<&str>) -> ArgMatches {
Command::new("Dust")
.trailing_var_arg(true)
.arg(Arg::new("depth").long("depth").takes_value(true))
.arg(
Arg::new("depth")
.long("depth")
.num_args(1)
.value_parser(value_parser!(usize)),
)
.get_matches_from(args)
}
}

View File

@@ -1,9 +1,11 @@
use std::fs;
use std::sync::Arc;
use std::sync::Mutex;
use crate::node::Node;
use crate::progress::Operation;
use crate::progress::PAtomicInfo;
use crate::progress::RuntimeErrors;
use crate::progress::ORDERING;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;
@@ -28,16 +30,17 @@ pub struct WalkData<'a> {
pub ignore_hidden: bool,
pub follow_links: bool,
pub progress_data: Arc<PAtomicInfo>,
pub errors: Arc<Mutex<RuntimeErrors>>,
}
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> Vec<Node> {
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: &WalkData) -> Vec<Node> {
let mut inodes = HashSet::new();
let top_level_nodes: Vec<_> = dirs
.into_iter()
.filter_map(|d| {
let prog_data = &walk_data.progress_data;
prog_data.clear_state(&d);
let node = walk(d, &walk_data, 0)?;
let node = walk(d, walk_data, 0)?;
prog_data.state.store(Operation::PREPARING, ORDERING);
@@ -126,55 +129,83 @@ fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
let prog_data = &walk_data.progress_data;
let mut children = vec![];
let errors = &walk_data.errors;
if let Ok(entries) = fs::read_dir(&dir) {
children = entries
.into_iter()
.par_bridge()
.filter_map(|entry| {
if let Ok(ref entry) = entry {
// uncommenting the below line gives simpler code but
// rayon doesn't parallelize as well giving a 3X performance drop
// hence we unravel the recursion a bit
let children = if dir.is_dir() {
let read_dir = fs::read_dir(&dir);
match read_dir {
Ok(entries) => {
entries
.into_iter()
.par_bridge()
.filter_map(|entry| {
if let Ok(ref entry) = entry {
// uncommenting the below line gives simpler code but
// rayon doesn't parallelize as well giving a 3X performance drop
// hence we unravel the recursion a bit
// return walk(entry.path(), walk_data, depth)
// return walk(entry.path(), walk_data, depth)
if !ignore_file(entry, walk_data) {
if let Ok(data) = entry.file_type() {
if data.is_dir() || (walk_data.follow_links && data.is_symlink()) {
return walk(entry.path(), walk_data, depth + 1);
if !ignore_file(entry, walk_data) {
if let Ok(data) = entry.file_type() {
if data.is_dir()
|| (walk_data.follow_links && data.is_symlink())
{
return walk(entry.path(), walk_data, depth + 1);
}
let node = build_node(
entry.path(),
vec![],
walk_data.filter_regex,
walk_data.invert_filter_regex,
walk_data.use_apparent_size,
data.is_symlink(),
data.is_file(),
walk_data.by_filecount,
depth,
);
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![],
walk_data.filter_regex,
walk_data.invert_filter_regex,
walk_data.use_apparent_size,
data.is_symlink(),
data.is_file(),
walk_data.by_filecount,
depth,
);
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;
} else {
let mut editable_error = errors.lock().unwrap();
editable_error.no_permissions = true
}
None
})
.collect()
}
Err(failed) => {
let mut editable_error = errors.lock().unwrap();
match failed.kind() {
std::io::ErrorKind::PermissionDenied => {
editable_error.no_permissions = true;
}
std::io::ErrorKind::NotFound => {
editable_error.file_not_found.insert(failed.to_string());
}
_ => {
editable_error.unknown_error.insert(failed.to_string());
}
} else {
prog_data.no_permissions.store(true, ORDERING)
}
None
})
.collect();
} else if !dir.is_file() {
walk_data.progress_data.no_permissions.store(true, ORDERING)
}
vec![]
}
}
} else {
if !dir.is_file() {
let mut editable_error = errors.lock().unwrap();
let bad_file = dir.as_os_str().to_string_lossy().into();
editable_error.file_not_found.insert(bad_file);
}
vec![]
};
build_node(
dir,
children,

View File

@@ -23,7 +23,8 @@ pub struct InitialDisplayData {
pub colors_on: bool,
pub by_filecount: bool,
pub is_screen_reader: bool,
pub iso: bool,
pub output_format: String,
pub bars_on_right: bool,
}
pub struct DisplayData {
@@ -98,7 +99,13 @@ impl DrawData<'_> {
let mut new_bar = "".to_string();
let idx = 5 - level.clamp(1, 4);
for c in self.percent_bar.chars() {
let itr: Box<dyn Iterator<Item = char>> = if self.display_data.initial.bars_on_right {
Box::new(self.percent_bar.chars())
} else {
Box::new(self.percent_bar.chars().rev())
};
for c in itr {
num_not_my_bar -= 1;
if num_not_my_bar <= 0 {
new_bar.push(BLOCKS[0]);
@@ -108,7 +115,11 @@ impl DrawData<'_> {
new_bar.push(c);
}
}
new_bar
if self.display_data.initial.bars_on_right {
new_bar
} else {
new_bar.chars().rev().collect()
}
}
}
@@ -131,7 +142,7 @@ pub fn draw_it(
let max_size = biggest.size;
max_size.separate_with_commas().chars().count()
} else {
find_biggest_size_str(root_node, idd.iso)
find_biggest_size_str(root_node, &idd.output_format)
};
assert!(
@@ -179,10 +190,12 @@ pub fn draw_it(
}
}
fn find_biggest_size_str(node: &DisplayNode, iso: bool) -> usize {
let mut mx = human_readable_number(node.size, iso).chars().count();
fn find_biggest_size_str(node: &DisplayNode, output_format: &str) -> usize {
let mut mx = human_readable_number(node.size, output_format)
.chars()
.count();
for n in node.children.iter() {
mx = max(mx, find_biggest_size_str(n, iso));
mx = max(mx, find_biggest_size_str(n, output_format));
}
mx
}
@@ -364,7 +377,7 @@ fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayD
let output = if display_data.initial.by_filecount {
node.size.separate_with_commas()
} else {
human_readable_number(node.size, display_data.initial.iso)
human_readable_number(node.size, &display_data.initial.output_format)
};
let spaces_to_add = display_data.num_chars_needed_on_left_most - output.chars().count();
let output = " ".repeat(spaces_to_add) + output.as_str();
@@ -396,19 +409,48 @@ fn get_pretty_name(
}
}
pub fn human_readable_number(size: u64, iso: bool) -> String {
// If we are working with SI units or not
pub fn get_type_of_thousand(output_str: &str) -> u64 {
let is_si = output_str.contains('i'); // si, KiB, MiB, etc
if is_si {
1000
} else {
1024
}
}
pub fn get_number_format(output_str: &str) -> Option<(u64, char)> {
if output_str.starts_with('b') {
return Some((1, 'B'));
}
for (i, u) in UNITS.iter().enumerate() {
let num: u64 = if iso { 1000 } else { 1024 };
let marker = num.pow((UNITS.len() - i) as u32);
if size >= marker {
if size / marker < 10 {
return format!("{:.1}{}", (size as f32 / marker as f32), u);
} else {
return format!("{}{}", (size / marker), u);
}
if output_str.starts_with((*u).to_ascii_lowercase()) {
let marker = get_type_of_thousand(output_str).pow((UNITS.len() - i) as u32);
return Some((marker, *u));
}
}
None
}
pub fn human_readable_number(size: u64, output_str: &str) -> String {
match get_number_format(output_str) {
Some((x, u)) => {
format!("{}{}", (size / x), u)
}
None => {
for (i, u) in UNITS.iter().enumerate() {
let marker = get_type_of_thousand(output_str).pow((UNITS.len() - i) as u32);
if size >= marker {
if size / marker < 10 {
return format!("{:.1}{}", (size as f32 / marker as f32), u);
} else {
return format!("{}{}", (size / marker), u);
}
}
}
format!("{size}B")
}
}
format!("{size}B")
}
mod tests {
@@ -425,7 +467,8 @@ mod tests {
colors_on: false,
by_filecount: false,
is_screen_reader: false,
iso: false,
output_format: "".into(),
bars_on_right: false,
};
DisplayData {
initial,
@@ -491,21 +534,90 @@ mod tests {
#[test]
fn test_human_readable_number() {
assert_eq!(human_readable_number(1, false), "1B");
assert_eq!(human_readable_number(956, false), "956B");
assert_eq!(human_readable_number(1004, false), "1004B");
assert_eq!(human_readable_number(1024, false), "1.0K");
assert_eq!(human_readable_number(1536, false), "1.5K");
assert_eq!(human_readable_number(1024 * 512, false), "512K");
assert_eq!(human_readable_number(1024 * 1024, false), "1.0M");
assert_eq!(
human_readable_number(1024 * 1024 * 1024 - 1, false),
"1023M"
);
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20, false), "20G");
assert_eq!(
human_readable_number(1024 * 1024 * 1024 * 1024, false),
"1.0T"
);
assert_eq!(human_readable_number(1, ""), "1B");
assert_eq!(human_readable_number(956, ""), "956B");
assert_eq!(human_readable_number(1004, ""), "1004B");
assert_eq!(human_readable_number(1024, ""), "1.0K");
assert_eq!(human_readable_number(1536, ""), "1.5K");
assert_eq!(human_readable_number(1024 * 512, ""), "512K");
assert_eq!(human_readable_number(1024 * 1024, ""), "1.0M");
assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1, ""), "1023M");
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20, ""), "20G");
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024, ""), "1.0T");
}
#[test]
fn test_human_readable_number_si() {
assert_eq!(human_readable_number(1024 * 100, ""), "100K");
assert_eq!(human_readable_number(1024 * 100, "si"), "102K");
}
#[test]
fn test_human_readable_number_kb() {
let hrn = human_readable_number;
assert_eq!(hrn(1023, "b"), "1023B");
assert_eq!(hrn(1000 * 1000, "bytes"), "1000000B");
assert_eq!(hrn(1023, "kb"), "0K");
assert_eq!(hrn(1023, "kib"), "1K");
assert_eq!(hrn(1024, "kb"), "1K");
assert_eq!(hrn(1024 * 512, "kb"), "512K");
assert_eq!(hrn(1024 * 1024, "kb"), "1024K");
assert_eq!(hrn(1024 * 1000 * 1000 * 20, "kb"), "20000000K");
assert_eq!(hrn(1024 * 1024 * 1000 * 20, "mb"), "20000M");
assert_eq!(hrn(1024 * 1024 * 1024 * 20, "gb"), "20G");
}
#[cfg(test)]
fn build_draw_data<'a>(disp: &'a DisplayData, size: u32) -> (DrawData<'a>, DisplayNode) {
let n = DisplayNode {
name: PathBuf::from("/short"),
size: 2_u64.pow(size),
children: vec![],
};
let first_size_bar = repeat(BLOCKS[0]).take(13).collect();
let dd = DrawData {
indent: "".into(),
percent_bar: first_size_bar,
display_data: disp,
};
(dd, n)
}
#[test]
fn test_draw_data() {
let disp = &get_fake_display_data(20);
let (dd, n) = build_draw_data(disp, 12);
let bar = dd.generate_bar(&n, 1);
assert_eq!(bar, "█████████████");
}
#[test]
fn test_draw_data2() {
let disp = &get_fake_display_data(20);
let (dd, n) = build_draw_data(disp, 11);
let bar = dd.generate_bar(&n, 2);
assert_eq!(bar, "███████░░░░░░");
}
#[test]
fn test_draw_data3() {
let mut disp = get_fake_display_data(20);
let (dd, n) = build_draw_data(&disp, 11);
let bar = dd.generate_bar(&n, 3);
assert_eq!(bar, "███████▒▒▒▒▒▒");
disp.initial.bars_on_right = true;
let (dd, n) = build_draw_data(&disp, 11);
let bar = dd.generate_bar(&n, 3);
assert_eq!(bar, "▒▒▒▒▒▒███████")
}
#[test]
fn test_draw_data4() {
let disp = &get_fake_display_data(20);
let (dd, n) = build_draw_data(disp, 10);
// After 4 we have no more levels of shading so 4+ is the same
let bar = dd.generate_bar(&n, 4);
assert_eq!(bar, "████▓▓▓▓▓▓▓▓▓");
let bar = dd.generate_bar(&n, 5);
assert_eq!(bar, "████▓▓▓▓▓▓▓▓▓");
}
}

View File

@@ -11,24 +11,26 @@ mod progress;
mod utils;
use crate::cli::build_cli;
use crate::progress::RuntimeErrors;
use clap::parser::ValuesRef;
use dir_walker::WalkData;
use display::InitialDisplayData;
use filter::AggregateData;
use progress::PIndicator;
use progress::ORDERING;
use regex::Error;
use std::collections::HashSet;
use std::io::BufRead;
use std::fs::read_to_string;
use std::panic;
use std::process;
use std::sync::Arc;
use std::sync::Mutex;
use sysinfo::{System, SystemExt};
use self::display::draw_it;
use clap::Values;
use config::get_config;
use dir_walker::walk_it;
use filter::get_biggest;
use filter_type::get_all_file_types;
use rayon::ThreadPoolBuildError;
use regex::Regex;
use std::cmp::max;
use std::path::PathBuf;
@@ -87,7 +89,7 @@ fn get_width_of_terminal() -> usize {
.unwrap_or(DEFAULT_TERMINAL_WIDTH)
}
fn get_regex_value(maybe_value: Option<Values>) -> Vec<Regex> {
fn get_regex_value(maybe_value: Option<ValuesRef<String>>) -> Vec<Regex> {
maybe_value
.unwrap_or_default()
.map(|reg| {
@@ -99,67 +101,72 @@ fn get_regex_value(maybe_value: Option<Values>) -> Vec<Regex> {
.collect()
}
// Returns a list of lines from stdin or `None` if there's nothing to read
fn get_lines_from_stdin() -> Option<Vec<String>> {
atty::isnt(atty::Stream::Stdin).then(|| {
std::io::stdin()
.lock()
.lines()
.collect::<Result<_, _>>()
.expect("Error reading from stdin")
})
}
fn main() {
let options = build_cli().get_matches();
let config = get_config();
let stdin_lines = get_lines_from_stdin();
let target_dirs = match options.values_of("inputs") {
Some(values) => values.collect(),
None => stdin_lines.as_ref().map_or(vec!["."], |lines| {
lines.iter().map(String::as_str).collect()
}),
let target_dirs = match options.get_many::<String>("params") {
Some(values) => values.map(|v| v.as_str()).collect::<Vec<&str>>(),
None => vec!["."],
};
let summarize_file_types = options.is_present("types");
let summarize_file_types = options.get_flag("types");
let filter_regexs = get_regex_value(options.values_of("filter"));
let invert_filter_regexs = get_regex_value(options.values_of("invert_filter"));
let filter_regexs = get_regex_value(options.get_many("filter"));
let invert_filter_regexs = get_regex_value(options.get_many("invert_filter"));
let terminal_width = options
.value_of_t("width")
.unwrap_or_else(|_| get_width_of_terminal());
let terminal_width: usize = match options.get_one::<usize>("width") {
Some(&val) => val,
None => get_width_of_terminal(),
};
let depth = config.get_depth(&options);
// If depth is set, then we set the default number_of_lines to be max
// instead of screen height
let default_height = if depth != usize::MAX {
usize::MAX
} else {
get_height_of_terminal()
};
let number_of_lines = options
.value_of("number_of_lines")
.and_then(|v| {
v.parse()
.map_err(|_| eprintln!("Ignoring bad value for number_of_lines"))
.ok()
})
.unwrap_or(default_height);
let number_of_lines = match options.get_one::<usize>("number_of_lines") {
Some(&val) => val,
None => {
if depth != usize::MAX {
usize::MAX
} else {
get_height_of_terminal()
}
}
};
let no_colors = init_color(config.get_no_colors(&options));
let ignore_directories = options
.values_of("ignore_directory")
.unwrap_or_default()
.map(PathBuf::from);
let ignore_directories = match options.get_many::<String>("ignore_directory") {
Some(values) => values
.map(|v| v.as_str())
.map(PathBuf::from)
.collect::<Vec<PathBuf>>(),
None => vec![],
};
let by_filecount = options.is_present("by_filecount");
let limit_filesystem = options.is_present("limit_filesystem");
let follow_links = options.is_present("dereference_links");
let ignore_from_file_result = match options.get_one::<String>("ignore_all_in_file") {
Some(val) => read_to_string(val)
.unwrap()
.lines()
.map(Regex::new)
.collect::<Vec<Result<Regex, Error>>>(),
None => vec![],
};
let ignore_from_file = ignore_from_file_result
.into_iter()
.filter_map(|x| x.ok())
.collect::<Vec<Regex>>();
let invert_filter_regexs = invert_filter_regexs
.into_iter()
.chain(ignore_from_file)
.collect::<Vec<Regex>>();
let by_filecount = options.get_flag("by_filecount");
let limit_filesystem = options.get_flag("limit_filesystem");
let follow_links = options.get_flag("dereference_links");
let simplified_dirs = simplify_dir_names(target_dirs);
let allowed_filesystems = limit_filesystem
@@ -167,16 +174,17 @@ fn main() {
.unwrap_or_default();
let ignored_full_path: HashSet<PathBuf> = ignore_directories
.into_iter()
.flat_map(|x| simplified_dirs.iter().map(move |d| d.join(&x)))
.collect();
let iso = config.get_iso(&options);
let output_format = config.get_output_format(&options);
let ignore_hidden = config.get_ignore_hidden(&options);
let mut indicator = PIndicator::build_me();
if !config.get_disable_progress(&options) {
indicator.spawn(iso);
indicator.spawn(output_format.clone())
}
let walk_data = WalkData {
@@ -189,37 +197,54 @@ fn main() {
ignore_hidden,
follow_links,
progress_data: indicator.data.clone(),
errors: Arc::new(Mutex::new(RuntimeErrors::default())),
};
let stack_size = config.get_custom_stack_size(&options);
init_rayon(&stack_size);
let result = panic::catch_unwind(|| init_rayon);
if result.is_err() {
eprintln!("Problem initializing rayon, try: export RAYON_NUM_THREADS=1")
}
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 {
true => get_all_file_types(&top_level_nodes, number_of_lines),
false => {
let agg_data = AggregateData {
min_size: config.get_min_size(&options, iso),
min_size: config.get_min_size(&options),
only_dir: config.get_only_dir(&options),
only_file: config.get_only_file(&options),
number_of_lines,
depth,
using_a_filter: options.values_of("filter").is_some()
|| options.value_of("invert_filter").is_some(),
using_a_filter: !filter_regexs.is_empty() || !invert_filter_regexs.is_empty(),
};
get_biggest(top_level_nodes, agg_data)
}
};
let failed_permissions = indicator.data.no_permissions.load(ORDERING);
indicator.stop();
// Must have stopped indicator before we print to stderr
indicator.stop();
let final_errors = walk_data.errors.lock().unwrap();
let failed_permissions = final_errors.no_permissions;
if !final_errors.file_not_found.is_empty() {
let err = final_errors
.file_not_found
.iter()
.map(|a| a.as_ref())
.collect::<Vec<&str>>()
.join(", ");
eprintln!("No such file or directory: {}", err);
}
if failed_permissions {
eprintln!("Did not have permissions for all directories");
}
if !final_errors.unknown_error.is_empty() {
let err = final_errors
.unknown_error
.iter()
.map(|a| a.as_ref())
.collect::<Vec<&str>>()
.join(", ");
eprintln!("Unknown Error: {}", err);
}
if let Some(root_node) = tree {
let idd = InitialDisplayData {
@@ -227,8 +252,9 @@ fn main() {
is_reversed: !config.get_reverse(&options),
colors_on: !no_colors,
by_filecount,
iso,
is_screen_reader: config.get_screen_reader(&options),
output_format,
bars_on_right: config.get_bars_on_right(&options),
};
draw_it(
idd,
@@ -240,18 +266,34 @@ fn main() {
}
}
fn init_rayon() -> Result<(), ThreadPoolBuildError> {
let large_stack = usize::pow(1024, 3);
let mut s = System::new();
s.refresh_memory();
let available = s.available_memory();
fn init_rayon(stack_size: &Option<usize>) {
// Rayon seems to raise this error on 32-bit builds
// The global thread pool has not been initialized.: ThreadPoolBuildError { kind: GlobalPoolAlreadyInitialized }
if cfg!(target_pointer_width = "64") {
let result = panic::catch_unwind(|| {
match stack_size {
Some(n) => rayon::ThreadPoolBuilder::new()
.stack_size(*n)
.build_global(),
None => {
let large_stack = usize::pow(1024, 3);
let mut s = System::new();
s.refresh_memory();
let available = s.available_memory();
if available > large_stack.try_into().unwrap() {
// Larger stack size to handle cases with lots of nested directories
rayon::ThreadPoolBuilder::new()
.stack_size(large_stack)
.build_global()
} else {
rayon::ThreadPoolBuilder::new().build_global()
if available > large_stack.try_into().unwrap() {
// Larger stack size to handle cases with lots of nested directories
rayon::ThreadPoolBuilder::new()
.stack_size(large_stack)
.build_global()
} else {
rayon::ThreadPoolBuilder::new().build_global()
}
}
}
});
if result.is_err() {
eprintln!("Problem initializing rayon, try: export RAYON_NUM_THREADS=1")
}
}
}

View File

@@ -26,7 +26,7 @@ pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u
}
#[cfg(target_family = "windows")]
pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
// On windows opening the file to get size, file ID and volume can be very
// expensive because 1) it causes a few system calls, and more importantly 2) it can cause
// windows defender to scan the file.
@@ -90,16 +90,27 @@ pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(
Ok(Handle::from_file(file))
}
fn get_metadata_expensive(d: &Path) -> Option<(u64, Option<(u64, u64)>)> {
fn get_metadata_expensive(
d: &Path,
use_apparent_size: bool,
) -> Option<(u64, Option<(u64, u64)>)> {
use winapi_util::file::information;
let h = handle_from_path_limited(d).ok()?;
let info = information(&h).ok()?;
Some((
info.file_size(),
Some((info.file_index(), info.volume_serial_number())),
))
if use_apparent_size {
use filesize::PathExt;
Some((
d.size_on_disk().ok()?,
Some((info.file_index(), info.volume_serial_number())),
))
} else {
Some((
info.file_size(),
Some((info.file_index(), info.volume_serial_number())),
))
}
}
use std::os::windows::fs::MetadataExt;
@@ -111,18 +122,31 @@ pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(
const FILE_ATTRIBUTE_SYSTEM: u32 = 0x04;
const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
const FILE_ATTRIBUTE_SPARSE_FILE: u32 = 0x00000200;
const FILE_ATTRIBUTE_PINNED: u32 = 0x00080000;
const FILE_ATTRIBUTE_UNPINNED: u32 = 0x00100000;
const FILE_ATTRIBUTE_RECALL_ON_OPEN: u32 = 0x00040000;
const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS: u32 = 0x00400000;
const FILE_ATTRIBUTE_OFFLINE: u32 = 0x00001000;
// normally FILE_ATTRIBUTE_SPARSE_FILE would be enough, however Windows sometimes likes to mask it out. see: https://stackoverflow.com/q/54560454
const IS_PROBABLY_ONEDRIVE: u32 = FILE_ATTRIBUTE_SPARSE_FILE
| FILE_ATTRIBUTE_PINNED
| FILE_ATTRIBUTE_UNPINNED
| FILE_ATTRIBUTE_RECALL_ON_OPEN
| FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS
| FILE_ATTRIBUTE_OFFLINE;
let attr_filtered = md.file_attributes()
& !(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
if (attr_filtered & FILE_ATTRIBUTE_ARCHIVE) != 0
if ((attr_filtered & FILE_ATTRIBUTE_ARCHIVE) != 0
|| (attr_filtered & FILE_ATTRIBUTE_DIRECTORY) != 0
|| md.file_attributes() == FILE_ATTRIBUTE_NORMAL
|| md.file_attributes() == FILE_ATTRIBUTE_NORMAL)
&& !((attr_filtered & IS_PROBABLY_ONEDRIVE != 0) && use_apparent_size)
{
Some((md.len(), None))
} else {
get_metadata_expensive(d)
get_metadata_expensive(d, use_apparent_size)
}
}
_ => get_metadata_expensive(d),
_ => get_metadata_expensive(d, use_apparent_size),
}
}

View File

@@ -1,8 +1,9 @@
use std::{
collections::HashSet,
io::Write,
path::Path,
sync::{
atomic::{AtomicBool, AtomicU64, AtomicU8, AtomicUsize, Ordering},
atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering},
mpsc::{self, RecvTimeoutError, Sender},
Arc, RwLock,
},
@@ -55,7 +56,6 @@ pub struct PAtomicInfo {
pub total_file_size: AtomicU64,
pub state: AtomicU8,
pub current_path: ThreadStringWrapper,
pub no_permissions: AtomicBool,
}
impl PAtomicInfo {
@@ -68,18 +68,25 @@ impl PAtomicInfo {
}
}
#[derive(Default)]
pub struct RuntimeErrors {
pub no_permissions: bool,
pub file_not_found: HashSet<String>,
pub unknown_error: HashSet<String>,
}
/* -------------------------------------------------------------------------- */
fn format_preparing_str(prog_char: char, data: &PAtomicInfo, is_iso: bool) -> String {
fn format_preparing_str(prog_char: char, data: &PAtomicInfo, output_display: &str) -> String {
let path_in = data.current_path.get();
let size = human_readable_number(data.total_file_size.load(ORDERING), is_iso);
let size = human_readable_number(data.total_file_size.load(ORDERING), output_display);
format!("Preparing: {path_in} {size} ... {prog_char}")
}
fn format_indexing_str(prog_char: char, data: &PAtomicInfo, is_iso: bool) -> String {
fn format_indexing_str(prog_char: char, data: &PAtomicInfo, output_display: &str) -> String {
let path_in = data.current_path.get();
let file_count = data.num_files.load(ORDERING);
let size = human_readable_number(data.total_file_size.load(ORDERING), is_iso);
let size = human_readable_number(data.total_file_size.load(ORDERING), output_display);
let file_str = format!("{file_count} files, {size}");
format!("Indexing: {path_in} {file_str} ... {prog_char}")
}
@@ -99,7 +106,7 @@ impl PIndicator {
}
}
pub fn spawn(&mut self, is_iso: bool) {
pub fn spawn(&mut self, output_display: String) {
let data = self.data.clone();
let (stop_handler, receiver) = mpsc::channel::<()>();
@@ -118,8 +125,8 @@ impl PIndicator {
let prog_char = PROGRESS_CHARS[progress_char_i];
msg = match data.state.load(ORDERING) {
Operation::INDEXING => format_indexing_str(prog_char, &data, is_iso),
Operation::PREPARING => format_preparing_str(prog_char, &data, is_iso),
Operation::INDEXING => format_indexing_str(prog_char, &data, &output_display),
Operation::PREPARING => format_preparing_str(prog_char, &data, &output_display),
_ => panic!("Unknown State"),
};

View File

@@ -1 +1,2 @@
hi
something
.secret

View File

View File

@@ -52,7 +52,15 @@ fn exact_output_test<T: AsRef<OsStr>>(valid_outputs: Vec<String>, command_args:
let output = str::from_utf8(&a.unwrap().stdout).unwrap().to_owned();
assert!(valid_outputs.iter().any(|i| output.contains(i)));
let will_fail = valid_outputs.iter().any(|i| output.contains(i));
if !will_fail {
eprintln!(
"output:\n{}\ndoes not contain any of:\n{}",
output,
valid_outputs.join("\n\n")
);
}
assert!(will_fail)
}
// "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
@@ -60,7 +68,7 @@ fn exact_output_test<T: AsRef<OsStr>>(valid_outputs: Vec<String>, command_args:
#[test]
pub fn test_main_basic() {
// -c is no color mode - This makes testing much simpler
exact_output_test(main_output(), vec!["-c", "/tmp/test_dir/"])
exact_output_test(main_output(), vec!["-c", "-B", "/tmp/test_dir/"])
}
#[cfg_attr(target_os = "windows", ignore)]
@@ -68,6 +76,7 @@ pub fn test_main_basic() {
pub fn test_main_multi_arg() {
let command_args = vec![
"-c",
"-B",
"/tmp/test_dir/many/",
"/tmp/test_dir",
"/tmp/test_dir",
@@ -102,7 +111,7 @@ fn main_output() -> Vec<String> {
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_long_paths() {
let command_args = vec!["-c", "-p", "/tmp/test_dir/"];
let command_args = vec!["-c", "-p", "-B", "/tmp/test_dir/"];
exact_output_test(main_output_long_paths(), command_args);
}
@@ -130,7 +139,7 @@ fn main_output_long_paths() -> Vec<String> {
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_substring_of_names_and_long_names() {
let command_args = vec!["-c", "/tmp/test_dir2"];
let command_args = vec!["-c", "-B", "/tmp/test_dir2"];
exact_output_test(no_substring_of_names_output(), command_args);
}
@@ -164,7 +173,7 @@ fn no_substring_of_names_output() -> Vec<String> {
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_unicode_directories() {
let command_args = vec!["-c", "/tmp/test_dir_unicode"];
let command_args = vec!["-c", "-B", "/tmp/test_dir_unicode"];
exact_output_test(unicode_dir(), command_args);
}
@@ -197,12 +206,19 @@ pub fn test_apparent_size() {
fn apparent_size_output() -> Vec<String> {
// The apparent directory sizes are too unpredictable and system dependent to try and match
let files = r#"
let one_space_before = r#"
0B ┌── a_file
6B ├── hello_file
"#
.trim()
.to_string();
let two_space_before = r#"
0B ┌── a_file
6B ├── hello_file
"#
.trim()
.to_string();
vec![files]
vec![one_space_before, two_space_before]
}

View File

@@ -64,6 +64,7 @@ pub fn test_d_flag_works_and_still_recurses_down() {
// We had a bug where running with '-d 1' would stop at the first directory and the code
// would fail to recurse down
let output = build_command(vec!["-d", "1", "-f", "-c", "tests/test_dir2/"]);
assert!(output.contains("1 ┌── dir"));
assert!(output.contains("4 ┌─┴ test_dir2"));
}
@@ -73,14 +74,25 @@ pub fn test_ignore_dir() {
let output = build_command(vec!["-c", "-X", "dir_substring", "tests/test_dir2/"]);
assert!(!output.contains("dir_substring"));
}
// Add test for multiple dirs - with -d 0 and maybe -d 1 check the
#[test]
pub fn test_ignore_all_in_file() {
let output = build_command(vec![
"-c",
"-I",
"tests/test_dir_hidden_entries/.hidden_file",
"tests/test_dir_hidden_entries/",
]);
assert!(output.contains(" test_dir_hidden_entries"));
assert!(!output.contains(".secret"));
}
#[test]
pub fn test_with_bad_param() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let result = cmd.arg("bad_place").unwrap();
let stderr = str::from_utf8(&result.stderr).unwrap();
assert!(stderr.contains("Did not have permissions for all directories"));
assert!(stderr.contains("No such file or directory"));
}
#[test]