Compare commits

...

35 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
andy.boot
01c0aaeade feat: New --collapse flag
--collapse will keep that directory collapsed and will not expand it.
2025-01-27 22:00:08 +00:00
andy.boot
6cbd736e11 fix: Bars in --skip-total flag
Before we calculated the % by taking the longest bar. If you use
--skip-total the longest bar is not the total. We need to sum up all the
children of root to work out what the largest size is.
2025-01-26 11:22:37 +00:00
andy.boot
8e087e09da fix: Handle Interrupted Error
Rust may throw Interrupted errors while scanning filesystem. These may
be retried:
https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.Interrupted
2025-01-26 11:22:04 +00:00
andy.boot
9ba0b6d1d0 feat: Support -o flag for JSON output
requested in: https://github.com/bootandy/dust/issues/450
2025-01-17 20:48:56 +00:00
andy.boot
775d841840 style: clippy 2025-01-15 21:37:29 +00:00
n4n5
609fc1e760 clippy 2025-01-15 19:14:00 +00:00
n4n5
eeb686562d push config option 2025-01-15 19:14:00 +00:00
n4n5
e0eaeccc0b add wget install 2025-01-15 19:08:18 +00:00
n4n5
2e56a261e0 clippy 2025-01-15 19:08:18 +00:00
n4n5
bfe7323b20 fix typo Operator 2025-01-15 19:08:18 +00:00
janbridley
1372815007 Format src/display.rs 2024-11-08 22:50:03 +00:00
janbridley
7c9e2f1833 Enable pretty format for petabyte data 2024-11-08 22:50:03 +00:00
Camille Louédoc-Eyriès
1d40ca0870 docs(readme): warn about snap-dust limitations 2024-10-17 23:03:35 +01:00
Yukai Chou
86b2bd944c refactor: simplify get_height_of_terminal() and get_width... 2024-09-16 22:01:10 +01:00
andy.boot
b63608604a docs: Update README.md 2024-09-03 21:57:36 +01:00
andy.boot
b24fab720d deps: cargo update 2024-09-03 21:54:20 +01:00
wugeer
d81b9065a1 fix: man-page and completions missing in debian package 2024-09-03 21:49:23 +01:00
andy.boot
38c4d23732 docs: Update README.md
include snap
2024-08-21 18:53:55 +01:00
wugeer
99bf0fc041 docs: update sample config.toml 2024-08-21 18:47:15 +01:00
wugeer
75d0566949 feat: use pre-commit hooks to standardize commit messages 2024-08-21 18:46:20 +01:00
NoisyCoil
489d9ada44 fix: 64-bit atomics for platforms with no 64-bit atomics
Closes: #423
2024-08-09 19:28:20 +01:00
wugeer
f48fcc790a feat: support Dust tree by age 2024-08-07 19:31:22 +01:00
wugeer
733117d0f6 fix: retrieve metadata for symbolic links without following them
Previously, the function get_metadata in platform.rs used `fs::metadata` which follows symbolic links
and returns metadata for the target file. This caused issues #421: du / dust disagreement
when trying to determine properties of the symbolic link itself
2024-07-31 00:16:59 +01:00
andy.boot
dbd18f90e7 docs: update release procedure
Previously I accidentally tagged a release before building it leading
to an out of date Cargo.lock file.
2024-07-17 19:35:44 +01:00
andy.boot
dad88ad660 version: increment version 2024-07-17 19:35:44 +01:00
andy.boot
00a7c410a0 ci: fix warning in windows build 2024-07-17 19:35:44 +01:00
andy.boot
1ab0b2f531 refactor: rename variable 2024-07-17 19:35:44 +01:00
andy.boot
c09073151d fix: perf issues with v1.1.0
Bring performance back
2024-07-17 19:16:46 +01:00
andy.boot
b4a517a096 version: update version
Also update docs, so I don't partially update a version number again
2024-07-16 22:48:27 +01:00
andy.boot
e654d30f9d version: increment version 2024-07-16 22:05:17 +01:00
Maksim Bondarenkov
4fc1897678 deps: update winapi-util to 0.1.8 (#410) 2024-07-03 21:41:52 +01:00
andy.boot
08b9c756ee fix: total_ordering of sort_by_inode
Before sort_by_inode could result in unstable ordering. This change
ensures we will always have a reliable total ordering
2024-06-30 21:57:58 +01:00
n4n5
394231683d feat: Add flag to read from file/stdin (#405)
* from standard

* improve

* happy clippy

* explicit arg option

* fix problem
2024-06-27 23:51:47 +01:00
29 changed files with 1032 additions and 334 deletions

View File

@@ -45,6 +45,11 @@ jobs:
override: true override: true
profile: minimal # minimal component installation (ie, no documentation) profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt, clippy components: rustfmt, clippy
- name: Install wget for Windows
if: matrix.job.os == 'windows-latest'
run: choco install wget --no-progress
- name: typos-action
uses: crate-ci/typos@v1.28.4
- name: "`fmt` testing" - name: "`fmt` testing"
if: steps.vars.outputs.JOB_DO_FORMAT_TESTING if: steps.vars.outputs.JOB_DO_FORMAT_TESTING
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1

11
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,11 @@
repos:
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: cargo-check
stages: [commit]
- id: fmt
stages: [commit]
- id: clippy
args: [--all-targets, --all-features]
stages: [commit]

301
Cargo.lock generated
View File

@@ -37,47 +37,48 @@ dependencies = [
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.13" version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
"anstyle-query", "anstyle-query",
"anstyle-wincon", "anstyle-wincon",
"colorchoice", "colorchoice",
"is_terminal_polyfill",
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.6" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.3" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.0.2" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.2" version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@@ -85,13 +86,14 @@ dependencies = [
[[package]] [[package]]
name = "assert_cmd" name = "assert_cmd"
version = "2.0.14" version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"bstr", "bstr",
"doc-comment", "doc-comment",
"libc",
"predicates", "predicates",
"predicates-core", "predicates-core",
"predicates-tree", "predicates-tree",
@@ -112,15 +114,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.9.1" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
dependencies = [ dependencies = [
"memchr", "memchr",
"regex-automata", "regex-automata",
@@ -135,9 +137,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.97" version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@@ -162,23 +164,23 @@ dependencies = [
"js-sys", "js-sys",
"num-traits", "num-traits",
"wasm-bindgen", "wasm-bindgen",
"windows-targets 0.52.4", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.4" version = "4.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" checksum = "c937d4061031a6d0c8da4b9a4f98a172fc2976dfb1c19213a9cf7d0d3c837e36"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.2" version = "4.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" checksum = "85379ba512b21a328adf887e85f7742d12e96eb31f3ef077df4ffc26b506ffed"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -188,24 +190,24 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.5.1" version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" checksum = "aa3c596da3cf0983427b0df0dba359df9182c13bd5b519b585a482b0c351f4e8"
dependencies = [ dependencies = [
"clap", "clap",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.0" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]] [[package]]
name = "clap_mangen" name = "clap_mangen"
version = "0.2.20" version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e" checksum = "f17415fd4dfbea46e3274fcd8d368284519b358654772afb700dc2e8d2b24eeb"
dependencies = [ dependencies = [
"clap", "clap",
"roff", "roff",
@@ -213,9 +215,9 @@ dependencies = [
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.0" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]] [[package]]
name = "config-file" name = "config-file"
@@ -255,9 +257,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.19" version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]] [[package]]
name = "ctrlc" name = "ctrlc"
@@ -303,7 +305,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "du-dust" name = "du-dust"
version = "1.0.0" version = "1.1.1"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"assert_cmd", "assert_cmd",
@@ -316,6 +318,7 @@ dependencies = [
"directories", "directories",
"filesize", "filesize",
"lscolors", "lscolors",
"portable-atomic",
"rayon", "rayon",
"regex", "regex",
"serde", "serde",
@@ -331,15 +334,15 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.10.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.8" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@@ -347,9 +350,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.0.2" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]] [[package]]
name = "filesize" name = "filesize"
@@ -362,9 +365,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.12" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@@ -411,6 +414,12 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.11"
@@ -434,13 +443,12 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.0.1" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.6.0",
"libc", "libc",
"redox_syscall",
] ]
[[package]] [[package]]
@@ -451,15 +459,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.13" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.21" version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]] [[package]]
name = "lscolors" name = "lscolors"
@@ -473,9 +481,9 @@ dependencies = [
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.1" version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "nix" name = "nix"
@@ -483,7 +491,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.6.0",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -530,10 +538,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "predicates" name = "portable-atomic"
version = "3.1.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
[[package]]
name = "predicates"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"difflib", "difflib",
@@ -542,15 +556,15 @@ dependencies = [
[[package]] [[package]]
name = "predicates-core" name = "predicates-core"
version = "1.0.6" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931"
[[package]] [[package]]
name = "predicates-tree" name = "predicates-tree"
version = "1.0.9" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13"
dependencies = [ dependencies = [
"predicates-core", "predicates-core",
"termtree", "termtree",
@@ -558,18 +572,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.79" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -594,20 +608,11 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.4" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"libredox", "libredox",
@@ -616,9 +621,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.4" version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -628,9 +633,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -639,15 +644,15 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.2" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]] [[package]]
name = "roff" name = "roff"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]] [[package]]
name = "rustix" name = "rustix"
@@ -665,37 +670,37 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.32" version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [ dependencies = [
"bitflags 2.5.0", "bitflags 2.6.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.13", "linux-raw-sys 0.4.14",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.197" version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.197" version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -704,11 +709,12 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.116" version = "1.0.122"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr",
"ryu", "ryu",
"serde", "serde",
] ]
@@ -721,15 +727,15 @@ checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.55" version = "2.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -753,14 +759,15 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.10.1" version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"rustix 0.38.32", "once_cell",
"windows-sys 0.52.0", "rustix 0.38.34",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -781,18 +788,18 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.58" version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.58" version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -822,15 +829,15 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.11" version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "wait-timeout" name = "wait-timeout"
@@ -919,11 +926,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.6" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"winapi", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -938,7 +945,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [ dependencies = [
"windows-targets 0.52.4", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@@ -956,7 +963,16 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.52.4", "windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
] ]
[[package]] [[package]]
@@ -976,17 +992,18 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.4" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.52.4", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.4", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.4", "windows_i686_gnu 0.52.6",
"windows_i686_msvc 0.52.4", "windows_i686_gnullvm",
"windows_x86_64_gnu 0.52.4", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnullvm 0.52.4", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_msvc 0.52.4", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
] ]
[[package]] [[package]]
@@ -997,9 +1014,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.4" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@@ -1009,9 +1026,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.4" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@@ -1021,9 +1038,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.4" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@@ -1033,9 +1056,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.4" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@@ -1045,9 +1068,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.4" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@@ -1057,9 +1080,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.4" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@@ -1069,6 +1092,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.4" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "du-dust" name = "du-dust"
description = "A more intuitive version of du" description = "A more intuitive version of du"
version = "1.0.0" 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 = "2021" edition = "2021"
readme = "README.md" readme = "README.md"
@@ -44,6 +44,9 @@ sysinfo = "0.27"
ctrlc = "3.4" ctrlc = "3.4"
chrono = "0.4" chrono = "0.4"
[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
portable-atomic = "1.4"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi-util = "0.1" winapi-util = "0.1"
filesize = "0.2.0" filesize = "0.2.0"
@@ -83,6 +86,16 @@ assets = [
"usr/share/doc/du-dust/README", "usr/share/doc/du-dust/README",
"644", "644",
], ],
[
"man-page/dust.1",
"usr/share/man/man1/dust.1",
"644",
],
[
"completions/dust.bash",
"usr/share/bash-completion/completions/dust",
"644",
],
] ]
extended-description = """\ extended-description = """\
Dust is meant to give you an instant overview of which directories are using Dust is meant to give you an instant overview of which directories are using

View File

@@ -27,11 +27,17 @@ Because I want an easy way to see where my disk is being used.
- `brew install dust` - `brew install dust`
#### [Snap](https://ubuntu.com/core/services/guide/snaps-intro) Ubuntu and [supported systems](https://snapcraft.io/docs/installing-snapd)
- `snap install dust`
Note: `dust` installed through `snap` can only access files stored in the `/home` directory. See daniejstriata/dust-snap#2 for more information.
#### [Pacstall](https://github.com/pacstall/pacstall) (Debian/Ubuntu) #### [Pacstall](https://github.com/pacstall/pacstall) (Debian/Ubuntu)
- `pacstall -I dust-bin` - `pacstall -I dust-bin`
### Anaconda (conda-forge) #### Anaconda (conda-forge)
- `conda install -c conda-forge dust` - `conda install -c conda-forge dust`
@@ -39,6 +45,10 @@ Because I want an easy way to see where my disk is being used.
- `deb-get install du-dust` - `deb-get install du-dust`
#### [x-cmd](https://www.x-cmd.com/pkg/#VPContent)
- `x env use dust`
#### Windows: #### Windows:
- `scoop install dust` - `scoop install dust`
@@ -92,6 +102,8 @@ 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 (Reads null-terminated file paths from FILE); If FILE is - then read from stdin
Usage: dust --collapse=node-modules will keep the node-modules folder collapsed in display instead of recursively opening it
``` ```
## Config file ## Config file

View File

@@ -1,14 +1,21 @@
# ----------- To do a release --------- # ----------- To do a release ---------
# Compare times of runs to check no drastic slow down:
# time target/release/dust ~/dev
# time dust ~dev
# edit version in cargo.toml # ----------- Pre release ---------
# Compare times of runs to check no drastic slow down:
# hyperfine 'target/release/dust /home/andy'
# hyperfine 'dust /home/andy'
# ----------- Release ---------
# inc version in cargo.toml
# cargo build --release
# commit changed files
# merge to master in github
# tag a commit and push (increment version in Cargo.toml first): # tag a commit and push (increment version in Cargo.toml first):
# git tag v0.4.5 # git tag v0.4.5
# git push origin v0.4.5 # git push origin v0.4.5
# cargo publish to put it in crates.io # cargo publish to put it in crates.io
# To install locally [Do before pushing it] # Optional: To install locally
#cargo install --path . #cargo install --path .

View File

@@ -14,15 +14,16 @@ _dust() {
fi fi
local context curcontext="$curcontext" state line local context curcontext="$curcontext" state line
_arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" : \
'-d+[Depth to show]:DEPTH: ' \ '-d+[Depth to show]:DEPTH: ' \
'--depth=[Depth to show]:DEPTH: ' \ '--depth=[Depth to show]:DEPTH: ' \
'-T+[Number of threads to use]: : ' \ '-T+[Number of threads to use]: : ' \
'--threads=[Number of threads to use]: : ' \ '--threads=[Number of threads to use]: : ' \
'--config=[Specify a config file to use]:FILE:_files' \
'-n+[Number of lines of output to show. (Default is terminal_height - 10)]:NUMBER: ' \ '-n+[Number of lines of output to show. (Default is terminal_height - 10)]:NUMBER: ' \
'--number-of-lines=[Number of lines of output to show. (Default is terminal_height - 10)]:NUMBER: ' \ '--number-of-lines=[Number of lines of output to show. (Default is terminal_height - 10)]:NUMBER: ' \
'*-X+[Exclude any file or directory with this name]:PATH:_files' \ '*-X+[Exclude any file or directory with this path]:PATH:_files' \
'*--ignore-directory=[Exclude any file or directory with this name]: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: ' \ '-z+[Minimum size file to include in output]:MIN_SIZE: ' \
@@ -43,6 +44,10 @@ _dust() {
'--atime=[just like -mtime, but based on file access time]: : ' \ '--atime=[just like -mtime, but based on file access time]: : ' \
'-y+[just like -mtime, but based on file change time]: : ' \ '-y+[just like -mtime, but based on file change time]: : ' \
'--ctime=[just like -mtime, but based on file change time]: : ' \ '--ctime=[just like -mtime, but based on file change time]: : ' \
'--files0-from=[run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input]: :_files' \
'*--collapse=[Keep these directories collapsed]: :_files' \
'-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)' \
'--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)' \
'-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]' \

View File

@@ -25,10 +25,11 @@ Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
[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('-n', 'n', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)') [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('--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 path')
[CompletionResult]::new('--ignore-directory', 'ignore-directory', [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 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')
@@ -49,6 +50,10 @@ Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
[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, 'run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input')
[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('--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')

View File

@@ -19,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 -h -V --depth --threads --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 --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
@@ -41,6 +41,21 @@ _dust() {
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
;; ;;
--config)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
--number-of-lines) --number-of-lines)
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
@@ -59,12 +74,12 @@ _dust() {
;; ;;
--ignore-all-in-file) --ignore-all-in-file)
local oldifs local oldifs
if [[ -v IFS ]]; then if [ -n "${IFS+x}" ]; then
oldifs="$IFS" oldifs="$IFS"
fi fi
IFS=$'\n' IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
if [[ -v oldifs ]]; then if [ -n "${oldifs+x}" ]; then
IFS="$oldifs" IFS="$oldifs"
fi fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
@@ -74,12 +89,12 @@ _dust() {
;; ;;
-I) -I)
local oldifs local oldifs
if [[ -v IFS ]]; then if [ -n "${IFS+x}" ]; then
oldifs="$IFS" oldifs="$IFS"
fi fi
IFS=$'\n' IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
if [[ -v oldifs ]]; then if [ -n "${oldifs+x}" ]; then
IFS="$oldifs" IFS="$oldifs"
fi fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
@@ -159,6 +174,22 @@ _dust() {
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0
;; ;;
--files0-from)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--collapse)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--filetime)
COMPREPLY=($(compgen -W "a c m" -- "${cur}"))
return 0
;;
-m)
COMPREPLY=($(compgen -W "a c m" -- "${cur}"))
return 0
;;
*) *)
COMPREPLY=() COMPREPLY=()
;; ;;

View File

@@ -22,10 +22,11 @@ set edit:completion:arg-completer[dust] = {|@words|
cand --depth 'Depth to show' cand --depth 'Depth to show'
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 -n 'Number of lines of output to show. (Default is terminal_height - 10)' cand -n 'Number of lines of output to show. (Default is terminal_height - 10)'
cand --number-of-lines 'Number of lines of output to show. (Default is terminal_height - 10)' 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 -X 'Exclude any file or directory with this path'
cand --ignore-directory 'Exclude any file or directory with this name' 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'
@@ -46,6 +47,10 @@ 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 'run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input'
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 --filetime 'Directory ''size'' is max filetime of child files instead of disk size. while a/c/m for last accessed/changed/modified time'
cand -p 'Subdirectories will not have their path shortened' cand -p 'Subdirectories will not have their path shortened'
cand --full-paths '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' cand -L 'dereference sym links - Treat sym links as directories and go into them'

View File

@@ -1,17 +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 -s n -l number-of-lines -d 'Number of lines of output to show. (Default is terminal_height - 10)' -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 -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 '',b '',k '',m '',g '',t '',kb '',mb '',gb '',tb ''}" 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''}"
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 '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 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'',c\t'',m\t''}"
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'

View File

@@ -25,4 +25,4 @@ skip-total=true
ignore-hidden=true ignore-hidden=true
# print sizes in powers of 1000 (e.g., 1.1G) # print sizes in powers of 1000 (e.g., 1.1G)
iso=true output-format="si"

View File

@@ -1,10 +1,10 @@
.ie \n(.g .ds Aq \(aq .ie \n(.g .ds Aq \(aq
.el .ds Aq ' .el .ds Aq '
.TH Dust 1 "Dust 1.0.0" .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\-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\-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
@@ -15,6 +15,9 @@ Depth to show
\fB\-T\fR, \fB\-\-threads\fR \fB\-T\fR, \fB\-\-threads\fR
Number of threads to use Number of threads to use
.TP .TP
\fB\-\-config\fR=\fIFILE\fR
Specify a config file to use
.TP
\fB\-n\fR, \fB\-\-number\-of\-lines\fR=\fINUMBER\fR \fB\-n\fR, \fB\-\-number\-of\-lines\fR=\fINUMBER\fR
Number of lines of output to show. (Default is terminal_height \- 10) Number of lines of output to show. (Default is terminal_height \- 10)
.TP .TP
@@ -22,7 +25,7 @@ Number of lines of output to show. (Default is terminal_height \- 10)
Subdirectories will not have their path shortened Subdirectories will not have their path shortened
.TP .TP
\fB\-X\fR, \fB\-\-ignore\-directory\fR=\fIPATH\fR \fB\-X\fR, \fB\-\-ignore\-directory\fR=\fIPATH\fR
Exclude any file or directory with this name Exclude any file or directory with this path
.TP .TP
\fB\-I\fR, \fB\-\-ignore\-all\-in\-file\fR=\fIFILE\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
@@ -112,6 +115,19 @@ just like \-mtime, but based on file access time
\fB\-y\fR, \fB\-\-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
run dust on NUL\-terminated file names specified in file; if argument is \-, then read names from standard input
.TP
\fB\-\-collapse\fR
Keep these directories collapsed
.TP
\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
.br
.br
[\fIpossible values: \fRa, c, m]
.TP
\fB\-h\fR, \fB\-\-help\fR \fB\-h\fR, \fB\-\-help\fR
Print help Print help
.TP .TP
@@ -121,4 +137,4 @@ Print version
[\fIPATH\fR] [\fIPATH\fR]
.SH VERSION .SH VERSION
v1.0.0 v1.1.1

View File

@@ -24,6 +24,15 @@ pub fn build_cli() -> Command {
.help("Number of threads to use") .help("Number of threads to use")
.num_args(1) .num_args(1)
) )
.arg(
Arg::new("config")
.long("config")
.help("Specify a config file to use")
.value_name("FILE")
.value_hint(clap::ValueHint::FilePath)
.value_parser(value_parser!(String))
.num_args(1)
)
.arg( .arg(
Arg::new("number_of_lines") Arg::new("number_of_lines")
.short('n') .short('n')
@@ -47,7 +56,7 @@ pub fn build_cli() -> Command {
.value_name("PATH") .value_name("PATH")
.value_hint(clap::ValueHint::AnyPath) .value_hint(clap::ValueHint::AnyPath)
.action(clap::ArgAction::Append) .action(clap::ArgAction::Append)
.help("Exclude any file or directory with this name"), .help("Exclude any file or directory with this path"),
) )
.arg( .arg(
Arg::new("ignore_all_in_file") Arg::new("ignore_all_in_file")
@@ -286,4 +295,32 @@ pub fn build_cli() -> Command {
.value_parser(value_parser!(String)) .value_parser(value_parser!(String))
.help("just like -mtime, but based on file change time") .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,3 +1,4 @@
use crate::node::FileTime;
use chrono::{Local, TimeZone}; use chrono::{Local, TimeZone};
use clap::ArgMatches; use clap::ArgMatches;
use config_file::FromConfigFile; use config_file::FromConfigFile;
@@ -7,14 +8,13 @@ use std::io::IsTerminal;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use crate::dir_walker::Operater; use crate::dir_walker::Operator;
use crate::display::get_number_format; use crate::display::get_number_format;
pub static DAY_SECONDS: i64 = 24 * 60 * 60; pub static DAY_SECONDS: i64 = 24 * 60 * 60;
#[derive(Deserialize, Default)] #[derive(Deserialize, Default)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[serde(deny_unknown_fields)]
pub struct Config { pub struct Config {
pub display_full_paths: Option<bool>, pub display_full_paths: Option<bool>,
pub display_apparent_size: Option<bool>, pub display_apparent_size: Option<bool>,
@@ -36,9 +36,17 @@ pub struct Config {
pub threads: Option<usize>, pub threads: Option<usize>,
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>,
} }
impl Config { impl Config {
pub fn get_files_from(&self, options: &ArgMatches) -> Option<String> {
let from_file = options.get_one::<String>("files0_from");
match from_file {
None => self.files0_from.as_ref().map(|x| x.to_string()),
Some(x) => Some(x.to_string()),
}
}
pub fn get_no_colors(&self, options: &ArgMatches) -> bool { pub fn get_no_colors(&self, options: &ArgMatches) -> bool {
Some(true) == self.no_colors || options.get_flag("no_colors") Some(true) == self.no_colors || options.get_flag("no_colors")
} }
@@ -76,6 +84,20 @@ impl Config {
}) })
.to_lowercase() .to_lowercase()
} }
pub fn get_filetime(&self, options: &ArgMatches) -> Option<FileTime> {
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: &ArgMatches) -> bool { pub fn get_skip_total(&self, options: &ArgMatches) -> bool {
Some(true) == self.skip_total || options.get_flag("skip_total") Some(true) == self.skip_total || options.get_flag("skip_total")
} }
@@ -137,21 +159,21 @@ impl Config {
Some(true) == self.output_json || options.get_flag("output_json") Some(true) == self.output_json || options.get_flag("output_json")
} }
pub fn get_modified_time_operator(&self, options: &ArgMatches) -> (Operater, i64) { pub fn get_modified_time_operator(&self, options: &ArgMatches) -> Option<(Operator, i64)> {
get_filter_time_operator( get_filter_time_operator(
options.get_one::<String>("mtime"), options.get_one::<String>("mtime"),
get_current_date_epoch_seconds(), get_current_date_epoch_seconds(),
) )
} }
pub fn get_accessed_time_operator(&self, options: &ArgMatches) -> (Operater, i64) { pub fn get_accessed_time_operator(&self, options: &ArgMatches) -> Option<(Operator, i64)> {
get_filter_time_operator( get_filter_time_operator(
options.get_one::<String>("atime"), options.get_one::<String>("atime"),
get_current_date_epoch_seconds(), get_current_date_epoch_seconds(),
) )
} }
pub fn get_created_time_operator(&self, options: &ArgMatches) -> (Operater, i64) { pub fn get_changed_time_operator(&self, options: &ArgMatches) -> Option<(Operator, i64)> {
get_filter_time_operator( get_filter_time_operator(
options.get_one::<String>("ctime"), options.get_one::<String>("ctime"),
get_current_date_epoch_seconds(), get_current_date_epoch_seconds(),
@@ -160,7 +182,7 @@ impl Config {
} }
fn get_current_date_epoch_seconds() -> i64 { fn get_current_date_epoch_seconds() -> i64 {
// calcurate current date epoch seconds // calculate current date epoch seconds
let now = Local::now(); let now = Local::now();
let current_date = now.date_naive(); let current_date = now.date_naive();
@@ -174,7 +196,7 @@ fn get_current_date_epoch_seconds() -> i64 {
fn get_filter_time_operator( fn get_filter_time_operator(
option_value: Option<&String>, option_value: Option<&String>,
current_date_epoch_seconds: i64, current_date_epoch_seconds: i64,
) -> (Operater, i64) { ) -> Option<(Operator, i64)> {
match option_value { match option_value {
Some(val) => { Some(val) => {
let time = current_date_epoch_seconds let time = current_date_epoch_seconds
@@ -184,12 +206,12 @@ fn get_filter_time_operator(
.abs() .abs()
* DAY_SECONDS; * DAY_SECONDS;
match val.chars().next().expect("Value should not be empty") { match val.chars().next().expect("Value should not be empty") {
'+' => (Operater::LessThan, time - DAY_SECONDS), '+' => Some((Operator::LessThan, time - DAY_SECONDS)),
'-' => (Operater::GreaterThan, time), '-' => Some((Operator::GreaterThan, time)),
_ => (Operater::Equal, time - DAY_SECONDS), _ => Some((Operator::Equal, time - DAY_SECONDS)),
} }
} }
None => (Operater::GreaterThan, 0), None => None,
} }
} }
@@ -208,7 +230,7 @@ fn convert_min_size(input: &str) -> Option<usize> {
match number_format { match number_format {
Some((multiple, _)) => Some(parsed_digits * (multiple as usize)), Some((multiple, _)) => Some(parsed_digits * (multiple as usize)),
None => { None => {
if letters.eq("") { if letters.is_empty() {
Some(parsed_digits) Some(parsed_digits)
} else { } else {
eprintln!("Ignoring invalid min-size: {input}"); eprintln!("Ignoring invalid min-size: {input}");
@@ -231,12 +253,29 @@ fn get_config_locations(base: &Path) -> Vec<PathBuf> {
] ]
} }
pub fn get_config() -> Config { pub fn get_config(conf_path: Option<String>) -> Config {
if let Some(home) = directories::BaseDirs::new() { match conf_path {
for path in get_config_locations(home.home_dir()) { Some(path_str) => {
let path = Path::new(&path_str);
if path.exists() { if path.exists() {
if let Ok(config) = Config::from_config_file(path) { match Config::from_config_file(path) {
return config; Ok(config) => return config,
Err(e) => {
eprintln!("Ignoring invalid config file '{}': {}", &path.display(), e)
}
}
} else {
eprintln!("Config file {:?} doesn't exists", &path.display());
}
}
None => {
if let Some(home) = directories::BaseDirs::new() {
for path in get_config_locations(home.home_dir()) {
if path.exists() {
if let Ok(config) = Config::from_config_file(&path) {
return config;
}
}
} }
} }
} }
@@ -251,6 +290,7 @@ mod tests {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
use clap::builder::PossibleValue;
use clap::{value_parser, Arg, ArgMatches, Command}; use clap::{value_parser, Arg, ArgMatches, Command};
#[test] #[test]
@@ -330,4 +370,56 @@ mod tests {
) )
.get_matches_from(args) .get_matches_from(args)
} }
#[test]
fn test_get_filetime() {
// No config and no flag.
let c = Config::default();
let args = get_filetime_args(vec!["dust"]);
assert_eq!(c.get_filetime(&args), None);
// Config is not defined and flag is defined as access time
let c = Config::default();
let args = get_filetime_args(vec!["dust", "--filetime", "a"]);
assert_eq!(c.get_filetime(&args), Some(FileTime::Accessed));
let c = Config::default();
let args = get_filetime_args(vec!["dust", "--filetime", "accessed"]);
assert_eq!(c.get_filetime(&args), Some(FileTime::Accessed));
// Config is not defined and flag is defined as modified time
let c = Config::default();
let args = get_filetime_args(vec!["dust", "--filetime", "m"]);
assert_eq!(c.get_filetime(&args), Some(FileTime::Modified));
let c = Config::default();
let args = get_filetime_args(vec!["dust", "--filetime", "modified"]);
assert_eq!(c.get_filetime(&args), Some(FileTime::Modified));
// Config is not defined and flag is defined as changed time
let c = Config::default();
let args = get_filetime_args(vec!["dust", "--filetime", "c"]);
assert_eq!(c.get_filetime(&args), Some(FileTime::Changed));
let c = Config::default();
let args = get_filetime_args(vec!["dust", "--filetime", "changed"]);
assert_eq!(c.get_filetime(&args), Some(FileTime::Changed));
}
fn get_filetime_args(args: Vec<&str>) -> ArgMatches {
Command::new("Dust")
.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 accessed/changed/modified time"),
)
.get_matches_from(args)
}
} }

View File

@@ -1,4 +1,6 @@
use std::cmp::Ordering;
use std::fs; use std::fs;
use std::io::Error;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
@@ -13,6 +15,7 @@ use crate::utils::is_filtered_out_due_to_regex;
use rayon::iter::ParallelBridge; use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator; use rayon::prelude::ParallelIterator;
use regex::Regex; use regex::Regex;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::collections::HashSet; use std::collections::HashSet;
@@ -20,10 +23,11 @@ use std::collections::HashSet;
use crate::node::build_node; use crate::node::build_node;
use std::fs::DirEntry; use std::fs::DirEntry;
use crate::node::FileTime;
use crate::platform::get_metadata; use crate::platform::get_metadata;
#[derive(Debug)] #[derive(Debug)]
pub enum Operater { pub enum Operator {
Equal = 0, Equal = 0,
LessThan = 1, LessThan = 1,
GreaterThan = 2, GreaterThan = 2,
@@ -34,11 +38,12 @@ pub struct WalkData<'a> {
pub filter_regex: &'a [Regex], pub filter_regex: &'a [Regex],
pub invert_filter_regex: &'a [Regex], pub invert_filter_regex: &'a [Regex],
pub allowed_filesystems: HashSet<u64>, pub allowed_filesystems: HashSet<u64>,
pub filter_modified_time: (Operater, i64), pub filter_modified_time: Option<(Operator, i64)>,
pub filter_accessed_time: (Operater, i64), pub filter_accessed_time: Option<(Operator, i64)>,
pub filter_changed_time: (Operater, i64), pub filter_changed_time: Option<(Operator, i64)>,
pub use_apparent_size: bool, pub use_apparent_size: bool,
pub by_filecount: bool, pub by_filecount: bool,
pub by_filetime: &'a Option<FileTime>,
pub ignore_hidden: bool, pub ignore_hidden: bool,
pub follow_links: bool, pub follow_links: bool,
pub progress_data: Arc<PAtomicInfo>, pub progress_data: Arc<PAtomicInfo>,
@@ -56,19 +61,15 @@ pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: &WalkData) -> Vec<Node> {
prog_data.state.store(Operation::PREPARING, ORDERING); prog_data.state.store(Operation::PREPARING, ORDERING);
clean_inodes(node, &mut inodes, walk_data.use_apparent_size) clean_inodes(node, &mut inodes, walk_data)
}) })
.collect(); .collect();
top_level_nodes top_level_nodes
} }
// 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( fn clean_inodes(x: Node, inodes: &mut HashSet<(u64, u64)>, walk_data: &WalkData) -> Option<Node> {
x: Node, if !walk_data.use_apparent_size {
inodes: &mut HashSet<(u64, u64)>,
use_apparent_size: bool,
) -> Option<Node> {
if !use_apparent_size {
if let Some(id) = x.inode_device { if let Some(id) = x.inode_device {
if !inodes.insert(id) { if !inodes.insert(id) {
return None; return None;
@@ -81,12 +82,25 @@ fn clean_inodes(
tmp.sort_by(sort_by_inode); tmp.sort_by(sort_by_inode);
let new_children: Vec<_> = tmp let new_children: Vec<_> = tmp
.into_iter() .into_iter()
.filter_map(|c| clean_inodes(c, inodes, use_apparent_size)) .filter_map(|c| clean_inodes(c, inodes, walk_data))
.collect(); .collect();
let actual_size = if walk_data.by_filetime.is_some() {
// If by_filetime is Some, directory 'size' is the maximum filetime among child files instead of disk size
new_children
.iter()
.map(|c| c.size)
.chain(std::iter::once(x.size))
.max()
.unwrap_or(0)
} else {
// If by_filetime is None, directory 'size' is the sum of disk sizes or file counts of child files
x.size + new_children.iter().map(|c| c.size).sum::<u64>()
};
Some(Node { Some(Node {
name: x.name, name: x.name,
size: x.size + new_children.iter().map(|c| c.size).sum::<u64>(), size: actual_size,
children: new_children, children: new_children,
inode_device: x.inode_device, inode_device: x.inode_device,
depth: x.depth, depth: x.depth,
@@ -95,43 +109,54 @@ fn clean_inodes(
fn sort_by_inode(a: &Node, b: &Node) -> std::cmp::Ordering { fn sort_by_inode(a: &Node, b: &Node) -> std::cmp::Ordering {
// Sorting by inode is quicker than by sorting by name/size // Sorting by inode is quicker than by sorting by name/size
if let Some(x) = a.inode_device { match (a.inode_device, b.inode_device) {
if let Some(y) = b.inode_device { (Some(x), Some(y)) => {
if x.0 != y.0 { if x.0 != y.0 {
return x.0.cmp(&y.0); x.0.cmp(&y.0)
} else if x.1 != y.1 { } else if x.1 != y.1 {
return x.1.cmp(&y.1); x.1.cmp(&y.1)
} else {
a.name.cmp(&b.name)
} }
} }
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => a.name.cmp(&b.name),
} }
a.name.cmp(&b.name)
} }
fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool { fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
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 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 size_inode_device = get_metadata(entry.path(), false); if !walk_data.allowed_filesystems.is_empty() {
if let Some((_size, Some((_id, dev)), (modified_time, accessed_time, changed_time))) = let size_inode_device = get_metadata(entry.path(), false, follow_links);
size_inode_device if let Some((_size, Some((_id, dev)), _gunk)) = size_inode_device {
{ if !walk_data.allowed_filesystems.contains(&dev) {
if !walk_data.allowed_filesystems.is_empty() return true;
&& !walk_data.allowed_filesystems.contains(&dev) }
{
return true;
} }
if entry.path().is_file() }
&& [ if walk_data.filter_accessed_time.is_some()
(&walk_data.filter_modified_time, modified_time), || walk_data.filter_modified_time.is_some()
(&walk_data.filter_accessed_time, accessed_time), || walk_data.filter_changed_time.is_some()
(&walk_data.filter_changed_time, changed_time), {
] let size_inode_device = get_metadata(entry.path(), false, follow_links);
.iter() if let Some((_, _, (modified_time, accessed_time, changed_time))) = size_inode_device {
.any(|(filter_time, actual_time)| { if entry.path().is_file()
is_filtered_out_due_to_file_time(filter_time, *actual_time) && [
}) (&walk_data.filter_modified_time, modified_time),
{ (&walk_data.filter_accessed_time, accessed_time),
return true; (&walk_data.filter_changed_time, changed_time),
]
.iter()
.any(|(filter_time, actual_time)| {
is_filtered_out_due_to_file_time(filter_time, *actual_time)
})
{
return true;
}
} }
} }
@@ -206,8 +231,9 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
} }
} }
Err(ref failed) => { Err(ref failed) => {
let mut editable_error = errors.lock().unwrap(); if handle_error_and_retry(failed, &dir, walk_data) {
editable_error.no_permissions.insert(failed.to_string()); return walk(dir.clone(), walk_data, depth);
}
} }
} }
None None
@@ -215,21 +241,11 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
.collect() .collect()
} }
Err(failed) => { Err(failed) => {
let mut editable_error = errors.lock().unwrap(); if handle_error_and_retry(&failed, &dir, walk_data) {
match failed.kind() { return walk(dir, walk_data, depth);
std::io::ErrorKind::PermissionDenied => { } else {
editable_error vec![]
.no_permissions
.insert(dir.to_string_lossy().into());
}
std::io::ErrorKind::NotFound => {
editable_error.file_not_found.insert(failed.to_string());
}
_ => {
editable_error.unknown_error.insert(failed.to_string());
}
} }
vec![]
} }
} }
} else { } else {
@@ -240,10 +256,51 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
} }
vec![] vec![]
}; };
build_node(dir, children, false, false, depth, walk_data) let is_symlink = if walk_data.follow_links {
match fs::symlink_metadata(&dir) {
Ok(metadata) => metadata.file_type().is_symlink(),
Err(_) => false,
}
} else {
false
};
build_node(dir, children, is_symlink, false, depth, walk_data)
}
fn handle_error_and_retry(failed: &Error, dir: &Path, walk_data: &WalkData) -> bool {
let mut editable_error = walk_data.errors.lock().unwrap();
match failed.kind() {
std::io::ErrorKind::PermissionDenied => {
editable_error
.no_permissions
.insert(dir.to_string_lossy().into());
}
std::io::ErrorKind::InvalidInput => {
editable_error
.no_permissions
.insert(dir.to_string_lossy().into());
}
std::io::ErrorKind::NotFound => {
editable_error.file_not_found.insert(failed.to_string());
}
std::io::ErrorKind::Interrupted => {
let mut editable_error = walk_data.errors.lock().unwrap();
editable_error.interrupted_error += 1;
if editable_error.interrupted_error > 3 {
panic!("Multiple Interrupted Errors occurred while scanning filesystem. Aborting");
} else {
return true;
}
}
_ => {
editable_error.unknown_error.insert(failed.to_string());
}
}
false
} }
mod tests { mod tests {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
@@ -258,17 +315,43 @@ mod tests {
} }
} }
#[cfg(test)]
fn create_walker<'a>(use_apparent_size: bool) -> WalkData<'a> {
use crate::PIndicator;
let indicator = PIndicator::build_me();
WalkData {
ignore_directories: HashSet::new(),
filter_regex: &[],
invert_filter_regex: &[],
allowed_filesystems: HashSet::new(),
filter_modified_time: Some((Operator::GreaterThan, 0)),
filter_accessed_time: Some((Operator::GreaterThan, 0)),
filter_changed_time: Some((Operator::GreaterThan, 0)),
use_apparent_size,
by_filecount: false,
by_filetime: &None,
ignore_hidden: false,
follow_links: false,
progress_data: indicator.data.clone(),
errors: Arc::new(Mutex::new(RuntimeErrors::default())),
}
}
#[test] #[test]
#[allow(clippy::redundant_clone)] #[allow(clippy::redundant_clone)]
fn test_should_ignore_file() { fn test_should_ignore_file() {
let mut inodes = HashSet::new(); let mut inodes = HashSet::new();
let n = create_node(); let n = create_node();
let walkdata = create_walker(false);
// First time we insert the node // First time we insert the node
assert_eq!(clean_inodes(n.clone(), &mut inodes, false), Some(n.clone())); assert_eq!(
clean_inodes(n.clone(), &mut inodes, &walkdata),
Some(n.clone())
);
// Second time is a duplicate - we ignore it // Second time is a duplicate - we ignore it
assert_eq!(clean_inodes(n.clone(), &mut inodes, false), None); assert_eq!(clean_inodes(n.clone(), &mut inodes, &walkdata), None);
} }
#[test] #[test]
@@ -276,9 +359,53 @@ mod tests {
fn test_should_not_ignore_files_if_using_apparent_size() { fn test_should_not_ignore_files_if_using_apparent_size() {
let mut inodes = HashSet::new(); let mut inodes = HashSet::new();
let n = create_node(); let n = create_node();
let walkdata = create_walker(true);
// If using apparent size we include Nodes, even if duplicate inodes // If using apparent size we include Nodes, even if duplicate inodes
assert_eq!(clean_inodes(n.clone(), &mut inodes, true), Some(n.clone())); assert_eq!(
assert_eq!(clean_inodes(n.clone(), &mut inodes, true), Some(n.clone())); clean_inodes(n.clone(), &mut inodes, &walkdata),
Some(n.clone())
);
assert_eq!(
clean_inodes(n.clone(), &mut inodes, &walkdata),
Some(n.clone())
);
}
#[test]
fn test_total_ordering_of_sort_by_inode() {
use std::str::FromStr;
let a = Node {
name: PathBuf::from_str("a").unwrap(),
size: 0,
children: vec![],
inode_device: Some((3, 66310)),
depth: 0,
};
let b = Node {
name: PathBuf::from_str("b").unwrap(),
size: 0,
children: vec![],
inode_device: None,
depth: 0,
};
let c = Node {
name: PathBuf::from_str("c").unwrap(),
size: 0,
children: vec![],
inode_device: Some((1, 66310)),
depth: 0,
};
assert_eq!(sort_by_inode(&a, &b), Ordering::Greater);
assert_eq!(sort_by_inode(&a, &c), Ordering::Greater);
assert_eq!(sort_by_inode(&c, &b), Ordering::Greater);
assert_eq!(sort_by_inode(&b, &a), Ordering::Less);
assert_eq!(sort_by_inode(&c, &a), Ordering::Less);
assert_eq!(sort_by_inode(&b, &c), Ordering::Less);
} }
} }

View File

@@ -1,4 +1,5 @@
use crate::display_node::DisplayNode; use crate::display_node::DisplayNode;
use crate::node::FileTime;
use ansi_term::Colour::Red; use ansi_term::Colour::Red;
use lscolors::{LsColors, Style}; use lscolors::{LsColors, Style};
@@ -7,21 +8,29 @@ use unicode_width::UnicodeWidthStr;
use stfu8::encode_u8; use stfu8::encode_u8;
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::hash::Hash;
use std::iter::repeat; use std::iter::repeat;
use std::os::unix::ffi::OsStrExt;
use std::path::Path; use std::path::Path;
use thousands::Separable; use thousands::Separable;
pub static UNITS: [char; 4] = ['T', 'G', 'M', 'K']; pub static UNITS: [char; 5] = ['P', 'T', 'G', 'M', 'K'];
static BLOCKS: [char; 5] = ['█', '▓', '▒', '░', ' ']; static BLOCKS: [char; 5] = ['█', '▓', '▒', '░', ' '];
const FILETIME_SHOW_LENGTH: usize = 19;
pub struct InitialDisplayData { pub struct InitialDisplayData {
pub short_paths: bool, pub short_paths: bool,
pub is_reversed: bool, pub is_reversed: bool,
pub colors_on: bool, pub colors_on: bool,
pub by_filecount: bool, pub by_filecount: bool,
pub by_filetime: Option<FileTime>,
pub is_screen_reader: bool, pub is_screen_reader: bool,
pub output_format: String, pub output_format: String,
pub bars_on_right: bool, pub bars_on_right: bool,
@@ -33,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 {
@@ -130,17 +140,14 @@ pub fn draw_it(
root_node: &DisplayNode, root_node: &DisplayNode,
skip_total: bool, skip_total: bool,
) { ) {
let biggest = match skip_total {
false => root_node, let duplicate_names = check_for_dup_names(&root_node);
true => root_node
.get_children_from_node(false)
.next()
.unwrap_or(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 = biggest.size; let max_size = root_node.size;
max_size.separate_with_commas().chars().count() max_size.separate_with_commas().chars().count()
} else if idd.by_filetime.is_some() {
FILETIME_SHOW_LENGTH
} else { } else {
find_biggest_size_str(root_node, &idd.output_format) find_biggest_size_str(root_node, &idd.output_format)
}; };
@@ -150,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
@@ -166,9 +175,10 @@ pub fn draw_it(
let display_data = DisplayData { let display_data = DisplayData {
initial: idd, initial: idd,
num_chars_needed_on_left_most, num_chars_needed_on_left_most,
base_size: biggest.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(),
@@ -189,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)
@@ -205,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
@@ -220,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)
} }
@@ -275,26 +362,8 @@ fn clean_indentation_string(s: &str) -> String {
is is
} }
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);
@@ -342,6 +411,8 @@ pub fn format_string(
if display_data.initial.is_screen_reader { if display_data.initial.is_screen_reader {
// if screen_reader then bars is 'depth' // if screen_reader then bars is 'depth'
format!("{pretty_name} {bars} {pretty_size}{percent}") format!("{pretty_name} {bars} {pretty_size}{percent}")
} else if display_data.initial.by_filetime.is_some() {
format!("{pretty_size} {indent}{pretty_name}")
} else { } else {
format!("{pretty_size} {indent} {pretty_name}{percent}") format!("{pretty_size} {indent} {pretty_name}{percent}")
} }
@@ -367,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)
} }
@@ -376,6 +447,8 @@ fn get_name_percent(
fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayData) -> String { fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayData) -> String {
let output = if display_data.initial.by_filecount { let output = if display_data.initial.by_filecount {
node.size.separate_with_commas() node.size.separate_with_commas()
} else if display_data.initial.by_filetime.is_some() {
get_pretty_file_modified_time(node.size as i64)
} else { } else {
human_readable_number(node.size, &display_data.initial.output_format) human_readable_number(node.size, &display_data.initial.output_format)
}; };
@@ -389,6 +462,14 @@ fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayD
} }
} }
fn get_pretty_file_modified_time(timestamp: i64) -> String {
let datetime: DateTime<Utc> = Utc.timestamp_opt(timestamp, 0).unwrap();
let local_datetime = datetime.with_timezone(&Local);
local_datetime.format("%Y-%m-%dT%H:%M:%S").to_string()
}
fn get_pretty_name( fn get_pretty_name(
node: &DisplayNode, node: &DisplayNode,
name_and_padding: String, name_and_padding: String,
@@ -469,6 +550,7 @@ mod tests {
is_reversed: false, is_reversed: false,
colors_on: false, colors_on: false,
by_filecount: false, by_filecount: false,
by_filetime: None,
is_screen_reader: false, is_screen_reader: false,
output_format: "".into(), output_format: "".into(),
bars_on_right: false, bars_on_right: false,
@@ -547,6 +629,14 @@ mod tests {
assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1, ""), "1023M"); 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 * 20, ""), "20G");
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024, ""), "1.0T"); assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024, ""), "1.0T");
assert_eq!(
human_readable_number(1024 * 1024 * 1024 * 1024 * 234, ""),
"234T"
);
assert_eq!(
human_readable_number(1024 * 1024 * 1024 * 1024 * 1024, ""),
"1.0P"
);
} }
#[test] #[test]
@@ -625,4 +715,37 @@ mod tests {
let bar = dd.generate_bar(&n, 5); let bar = dd.generate_bar(&n, 5);
assert_eq!(bar, "████▓▓▓▓▓▓▓▓▓"); assert_eq!(bar, "████▓▓▓▓▓▓▓▓▓");
} }
#[test]
fn test_get_pretty_file_modified_time() {
// Create a timestamp for 2023-07-12 00:00:00 in local time
let local_dt = Local.with_ymd_and_hms(2023, 7, 12, 0, 0, 0).unwrap();
let timestamp = local_dt.timestamp();
// Format expected output
let expected_output = local_dt.format("%Y-%m-%dT%H:%M:%S").to_string();
assert_eq!(get_pretty_file_modified_time(timestamp), expected_output);
// Test another timestamp
let local_dt = Local.with_ymd_and_hms(2020, 1, 1, 12, 0, 0).unwrap();
let timestamp = local_dt.timestamp();
let expected_output = local_dt.format("%Y-%m-%dT%H:%M:%S").to_string();
assert_eq!(get_pretty_file_modified_time(timestamp), expected_output);
// Test timestamp for epoch start (1970-01-01T00:00:00)
let local_dt = Local.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
let timestamp = local_dt.timestamp();
let expected_output = local_dt.format("%Y-%m-%dT%H:%M:%S").to_string();
assert_eq!(get_pretty_file_modified_time(timestamp), expected_output);
// Test a future timestamp
let local_dt = Local.with_ymd_and_hms(2030, 12, 25, 6, 30, 0).unwrap();
let timestamp = local_dt.timestamp();
let expected_output = local_dt.format("%Y-%m-%dT%H:%M:%S").to_string();
assert_eq!(get_pretty_file_modified_time(timestamp), expected_output);
}
} }

View File

@@ -1,8 +1,12 @@
use std::cell::RefCell;
use std::path::PathBuf; use std::path::PathBuf;
use serde::Serialize; use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize)] use crate::display::human_readable_number;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct DisplayNode { pub struct DisplayNode {
// Note: the order of fields in important here, for PartialEq and PartialOrd // Note: the order of fields in important here, for PartialEq and PartialOrd
pub size: u64, pub size: u64,
@@ -25,3 +29,30 @@ impl DisplayNode {
out out
} }
} }
// Only used for -j 'json' flag combined with -o 'output_type' flag
// Used to pass the output_type into the custom Serde serializer
thread_local! {
pub static OUTPUT_TYPE: RefCell<String> = const { RefCell::new(String::new()) };
}
/*
We need the custom Serialize incase someone uses the -o flag to pass a custom output type in
(show size in Mb / Gb etc).
Sadly this also necessitates a global variable OUTPUT_TYPE as we can not pass the output_type flag
into the serialize method
*/
impl Serialize for DisplayNode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let readable_size = OUTPUT_TYPE
.with(|output_type| human_readable_number(self.size, output_type.borrow().as_str()));
let mut state = serializer.serialize_struct("DisplayNode", 2)?;
state.serialize_field("size", &(readable_size))?;
state.serialize_field("name", &self.name)?;
state.serialize_field("children", &self.children)?;
state.end()
}
}

View File

@@ -1,7 +1,9 @@
use crate::display_node::DisplayNode; use crate::display_node::DisplayNode;
use crate::node::FileTime;
use crate::node::Node; use crate::node::Node;
use std::collections::BinaryHeap; use std::collections::BinaryHeap;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@@ -14,7 +16,12 @@ pub struct AggregateData {
pub using_a_filter: bool, pub using_a_filter: bool,
} }
pub fn get_biggest(top_level_nodes: Vec<Node>, display_data: AggregateData) -> Option<DisplayNode> { pub fn get_biggest(
mut top_level_nodes: Vec<Node>,
display_data: AggregateData,
by_filetime: &Option<FileTime>,
keep_collapsed: HashSet<PathBuf>,
) -> Option<DisplayNode> {
if top_level_nodes.is_empty() { if top_level_nodes.is_empty() {
// perhaps change this, bring back Error object? // perhaps change this, bring back Error object?
return None; return None;
@@ -24,7 +31,16 @@ pub fn get_biggest(top_level_nodes: Vec<Node>, display_data: AggregateData) -> O
let root; let root;
if number_top_level_nodes > 1 { if number_top_level_nodes > 1 {
let size = top_level_nodes.iter().map(|node| node.size).sum(); let size = if by_filetime.is_some() {
top_level_nodes
.iter()
.map(|node| node.size)
.max()
.unwrap_or(0)
} else {
top_level_nodes.iter().map(|node| node.size).sum()
};
root = Node { root = Node {
name: PathBuf::from("(total)"), name: PathBuf::from("(total)"),
size, size,
@@ -32,6 +48,7 @@ pub fn get_biggest(top_level_nodes: Vec<Node>, display_data: AggregateData) -> O
inode_device: None, inode_device: None,
depth: 0, depth: 0,
}; };
// Always include the base nodes if we add a 'parent' (total) node // 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 {
@@ -39,13 +56,20 @@ pub fn get_biggest(top_level_nodes: Vec<Node>, display_data: AggregateData) -> O
heap = add_children(&display_data, &root, heap); heap = add_children(&display_data, &root, heap);
} }
Some(fill_remaining_lines(heap, &root, display_data)) let result = fill_remaining_lines(
heap,
&root,
display_data,
keep_collapsed,
);
Some(result)
} }
pub fn fill_remaining_lines<'a>( pub fn fill_remaining_lines<'a>(
mut heap: BinaryHeap<&'a Node>, mut heap: BinaryHeap<&'a Node>,
root: &'a Node, root: &'a Node,
display_data: AggregateData, display_data: AggregateData,
keep_collapsed: HashSet<PathBuf>,
) -> DisplayNode { ) -> DisplayNode {
let mut allowed_nodes = HashMap::new(); let mut allowed_nodes = HashMap::new();
@@ -56,7 +80,9 @@ pub fn fill_remaining_lines<'a>(
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);
} }
heap = add_children(&display_data, line, heap); if !keep_collapsed.contains(&line.name) {
heap = add_children(&display_data, line, heap);
}
} }
None => break, None => break,
} }
@@ -114,7 +140,7 @@ fn recursive_rebuilder(allowed_nodes: &HashMap<&Path, &Node>, current: &Node) ->
.map(|c| recursive_rebuilder(allowed_nodes, c)) .map(|c| recursive_rebuilder(allowed_nodes, c))
.collect(); .collect();
build_node(new_children, current) build_display_node(new_children, current)
} }
// Applies all allowed nodes as children to current node // Applies all allowed nodes as children to current node
@@ -127,13 +153,14 @@ fn flat_rebuilder(allowed_nodes: HashMap<&Path, &Node>, current: &Node) -> Displ
children: vec![], children: vec![],
}) })
.collect::<Vec<DisplayNode>>(); .collect::<Vec<DisplayNode>>();
build_node(new_children, current) build_display_node(new_children, current)
} }
fn build_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,
} }

View File

@@ -1,4 +1,5 @@
use crate::display_node::DisplayNode; use crate::display_node::DisplayNode;
use crate::node::FileTime;
use crate::node::Node; use crate::node::Node;
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::OsStr; use std::ffi::OsStr;
@@ -10,7 +11,11 @@ struct ExtensionNode<'a> {
extension: Option<&'a OsStr>, extension: Option<&'a OsStr>,
} }
pub fn get_all_file_types(top_level_nodes: &[Node], n: usize) -> Option<DisplayNode> { pub fn get_all_file_types(
top_level_nodes: &[Node],
n: usize,
by_filetime: &Option<FileTime>,
) -> 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);
@@ -44,16 +49,27 @@ pub fn get_all_file_types(top_level_nodes: &[Node], n: usize) -> Option<DisplayN
// ...then, aggregate the remaining nodes (if any) into a single "(others)" node // ...then, aggregate the remaining nodes (if any) into a single "(others)" node
if ext_nodes_iter.len() > 0 { if ext_nodes_iter.len() > 0 {
let actual_size = if by_filetime.is_some() {
ext_nodes_iter.map(|node| node.size).max().unwrap_or(0)
} else {
ext_nodes_iter.map(|node| node.size).sum()
};
displayed.push(DisplayNode { displayed.push(DisplayNode {
name: PathBuf::from("(others)"), name: PathBuf::from("(others)"),
size: ext_nodes_iter.map(|node| node.size).sum(), size: actual_size,
children: vec![], children: vec![],
}); });
} }
let actual_size: u64 = if by_filetime.is_some() {
displayed.iter().map(|node| node.size).max().unwrap_or(0)
} else {
displayed.iter().map(|node| node.size).sum()
};
let result = DisplayNode { let result = DisplayNode {
name: PathBuf::from("(total)"), name: PathBuf::from("(total)"),
size: displayed.iter().map(|node| node.size).sum(), size: actual_size,
children: displayed, children: displayed,
}; };

View File

@@ -21,8 +21,11 @@ use regex::Error;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::io;
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, SystemExt}; use sysinfo::{System, SystemExt};
@@ -30,6 +33,7 @@ use sysinfo::{System, SystemExt};
use self::display::draw_it; use self::display::draw_it;
use config::get_config; use config::get_config;
use dir_walker::walk_it; use dir_walker::walk_it;
use display_node::OUTPUT_TYPE;
use filter::get_biggest; use filter::get_biggest;
use filter_type::get_all_file_types; use filter_type::get_all_file_types;
use regex::Regex; use regex::Regex;
@@ -78,23 +82,19 @@ fn should_init_color(no_color: bool, force_color: bool) -> bool {
} }
fn get_height_of_terminal() -> usize { fn get_height_of_terminal() -> usize {
// Simplify once https://github.com/eminence/terminal-size/pull/41 is
// merged
terminal_size() terminal_size()
// Windows CI runners detect a terminal height of 0 // Windows CI runners detect a terminal height of 0
.map(|(_, Height(h))| max(h as usize, DEFAULT_NUMBER_OF_LINES)) .map(|(_, Height(h))| max(h.into(), DEFAULT_NUMBER_OF_LINES))
.unwrap_or(DEFAULT_NUMBER_OF_LINES) .unwrap_or(DEFAULT_NUMBER_OF_LINES)
- 10 - 10
} }
fn get_width_of_terminal() -> usize { fn get_width_of_terminal() -> usize {
// Simplify once https://github.com/eminence/terminal-size/pull/41 is
// merged
terminal_size() terminal_size()
.map(|(Width(w), _)| match cfg!(windows) { .map(|(Width(w), _)| match cfg!(windows) {
// Windows CI runners detect a very low terminal width // Windows CI runners detect a very low terminal width
true => max(w as usize, DEFAULT_TERMINAL_WIDTH), true => max(w.into(), DEFAULT_TERMINAL_WIDTH),
false => w as usize, false => w.into(),
}) })
.unwrap_or(DEFAULT_TERMINAL_WIDTH) .unwrap_or(DEFAULT_TERMINAL_WIDTH)
} }
@@ -113,23 +113,55 @@ fn get_regex_value(maybe_value: Option<ValuesRef<String>>) -> Vec<Regex> {
fn main() { fn main() {
let options = build_cli().get_matches(); let options = build_cli().get_matches();
let config = get_config(); 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 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; error_listen_for_ctrlc.lock().unwrap().abort = true;
println!("\nAborting"); println!("\nAborting");
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 = match options.get_many::<String>("params") { is_in_listing.store(true, Ordering::Relaxed);
Some(values) => values.map(|v| v.as_str()).collect::<Vec<&str>>(), let target_dirs = match config.get_files_from(&options) {
None => vec!["."], Some(path) => {
if path == "-" {
let mut targets_to_add = io::stdin()
.lines()
.map_while(Result::ok)
.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.get_flag("types"); let summarize_file_types = options.get_flag("types");
@@ -189,11 +221,12 @@ fn main() {
.collect::<Vec<Regex>>(); .collect::<Vec<Regex>>();
let by_filecount = options.get_flag("by_filecount"); let by_filecount = options.get_flag("by_filecount");
let by_filetime = config.get_filetime(&options);
let limit_filesystem = options.get_flag("limit_filesystem"); let limit_filesystem = options.get_flag("limit_filesystem");
let follow_links = options.get_flag("dereference_links"); let follow_links = options.get_flag("dereference_links");
let allowed_filesystems = limit_filesystem let allowed_filesystems = limit_filesystem
.then(|| get_filesystem_devices(&target_dirs)) .then(|| get_filesystem_devices(&target_dirs, follow_links))
.unwrap_or_default(); .unwrap_or_default();
let simplified_dirs = simplify_dir_names(&target_dirs); let simplified_dirs = simplify_dir_names(&target_dirs);
@@ -211,9 +244,22 @@ fn main() {
indicator.spawn(output_format.clone()) indicator.spawn(output_format.clone())
} }
let keep_collapsed: HashSet<PathBuf> = match options.get_many::<String>("collapse") {
Some(collapse) => {
let mut combined_dirs = HashSet::new();
for collapse_dir in collapse {
for target_dir in target_dirs.iter() {
combined_dirs.insert(PathBuf::from(target_dir).join(collapse_dir));
}
}
combined_dirs
}
None => HashSet::new(),
};
let filter_modified_time = config.get_modified_time_operator(&options); let filter_modified_time = config.get_modified_time_operator(&options);
let filter_accessed_time = config.get_accessed_time_operator(&options); let filter_accessed_time = config.get_accessed_time_operator(&options);
let filter_changed_time = config.get_created_time_operator(&options); let filter_changed_time = config.get_changed_time_operator(&options);
let walk_data = WalkData { let walk_data = WalkData {
ignore_directories: ignored_full_path, ignore_directories: ignored_full_path,
@@ -225,6 +271,7 @@ fn main() {
filter_changed_time, filter_changed_time,
use_apparent_size: config.get_apparent_size(&options), use_apparent_size: config.get_apparent_size(&options),
by_filecount, by_filecount,
by_filetime: &by_filetime,
ignore_hidden, ignore_hidden,
follow_links, follow_links,
progress_data: indicator.data.clone(), progress_data: indicator.data.clone(),
@@ -237,7 +284,7 @@ fn main() {
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), 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),
@@ -247,7 +294,7 @@ fn main() {
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(),
}; };
get_biggest(top_level_nodes, agg_data) get_biggest(top_level_nodes, agg_data, &by_filetime, keep_collapsed)
} }
}; };
@@ -294,19 +341,23 @@ fn main() {
} }
if let Some(root_node) = tree { if let Some(root_node) = tree {
let idd = InitialDisplayData {
short_paths: !config.get_full_paths(&options),
is_reversed: !config.get_reverse(&options),
colors_on: is_colors,
by_filecount,
is_screen_reader: config.get_screen_reader(&options),
output_format,
bars_on_right: config.get_bars_on_right(&options),
};
if config.get_output_json(&options) { if config.get_output_json(&options) {
OUTPUT_TYPE.with(|wrapped| {
wrapped.replace(output_format);
});
println!("{}", serde_json::to_string(&root_node).unwrap()); println!("{}", serde_json::to_string(&root_node).unwrap());
} else { } 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,
is_screen_reader: config.get_screen_reader(&options),
output_format,
bars_on_right: config.get_bars_on_right(&options),
};
draw_it( draw_it(
idd, idd,
config.get_no_bars(&options), config.get_no_bars(&options),

View File

@@ -16,6 +16,13 @@ pub struct Node {
pub depth: usize, pub depth: usize,
} }
#[derive(Debug, PartialEq)]
pub enum FileTime {
Modified,
Accessed,
Changed,
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn build_node( pub fn build_node(
dir: PathBuf, dir: PathBuf,
@@ -27,17 +34,18 @@ pub fn build_node(
) -> Option<Node> { ) -> Option<Node> {
let use_apparent_size = walk_data.use_apparent_size; let use_apparent_size = walk_data.use_apparent_size;
let by_filecount = walk_data.by_filecount; let by_filecount = walk_data.by_filecount;
let by_filetime = &walk_data.by_filetime;
get_metadata(&dir, use_apparent_size).map(|data| { get_metadata(
let inode_device = if is_symlink && !use_apparent_size { &dir,
None use_apparent_size,
} else { walk_data.follow_links && is_symlink,
data.1 )
}; .map(|data| {
let inode_device = data.1;
let size = if is_filtered_out_due_to_regex(walk_data.filter_regex, &dir) let size = if is_filtered_out_due_to_regex(walk_data.filter_regex, &dir)
|| 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)
|| (is_symlink && !use_apparent_size)
|| by_filecount && !is_file || by_filecount && !is_file
|| [ || [
(&walk_data.filter_modified_time, data.2 .0), (&walk_data.filter_modified_time, data.2 .0),
@@ -51,6 +59,13 @@ pub fn build_node(
0 0
} else if by_filecount { } else if by_filecount {
1 1
} else if by_filetime.is_some() {
match by_filetime {
Some(FileTime::Modified) => data.2 .0.unsigned_abs(),
Some(FileTime::Accessed) => data.2 .1.unsigned_abs(),
Some(FileTime::Changed) => data.2 .2.unsigned_abs(),
None => unreachable!(),
}
} else { } else {
data.0 data.0
}; };

View File

@@ -17,9 +17,15 @@ type FileTime = (i64, i64, i64);
pub fn get_metadata<P: AsRef<Path>>( pub fn get_metadata<P: AsRef<Path>>(
path: P, path: P,
use_apparent_size: bool, use_apparent_size: bool,
follow_links: bool,
) -> Option<(u64, Option<InodeAndDevice>, FileTime)> { ) -> Option<(u64, Option<InodeAndDevice>, FileTime)> {
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
match path.as_ref().metadata() { let metadata = if follow_links {
path.as_ref().metadata()
} else {
path.as_ref().symlink_metadata()
};
match metadata {
Ok(md) => { Ok(md) => {
if use_apparent_size { if use_apparent_size {
Some(( Some((
@@ -43,6 +49,7 @@ pub fn get_metadata<P: AsRef<Path>>(
pub fn get_metadata<P: AsRef<Path>>( pub fn get_metadata<P: AsRef<Path>>(
path: P, path: P,
use_apparent_size: bool, use_apparent_size: bool,
follow_links: bool,
) -> Option<(u64, Option<InodeAndDevice>, FileTime)> { ) -> Option<(u64, Option<InodeAndDevice>, FileTime)> {
// On windows opening the file to get size, file ID and volume can be very // 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 // expensive because 1) it causes a few system calls, and more importantly 2) it can cause
@@ -142,7 +149,12 @@ pub fn get_metadata<P: AsRef<Path>>(
use std::os::windows::fs::MetadataExt; use std::os::windows::fs::MetadataExt;
let path = path.as_ref(); let path = path.as_ref();
match path.metadata() { let metadata = if follow_links {
path.metadata()
} else {
path.symlink_metadata()
};
match metadata {
Ok(ref md) => { Ok(ref md) => {
const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20; const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20;
const FILE_ATTRIBUTE_READONLY: u32 = 0x01; const FILE_ATTRIBUTE_READONLY: u32 = 0x01;

View File

@@ -3,7 +3,7 @@ use std::{
io::Write, io::Write,
path::Path, path::Path,
sync::{ sync::{
atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering}, atomic::{AtomicU8, AtomicUsize, Ordering},
mpsc::{self, RecvTimeoutError, Sender}, mpsc::{self, RecvTimeoutError, Sender},
Arc, RwLock, Arc, RwLock,
}, },
@@ -11,6 +11,11 @@ use std::{
time::Duration, time::Duration,
}; };
#[cfg(not(target_has_atomic = "64"))]
use portable_atomic::AtomicU64;
#[cfg(target_has_atomic = "64")]
use std::sync::atomic::AtomicU64;
use crate::display::human_readable_number; use crate::display::human_readable_number;
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@@ -73,6 +78,7 @@ pub struct RuntimeErrors {
pub no_permissions: HashSet<String>, pub no_permissions: HashSet<String>,
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 abort: bool, pub abort: bool,
} }

View File

@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use crate::config::DAY_SECONDS; use crate::config::DAY_SECONDS;
use crate::dir_walker::Operater; use crate::dir_walker::Operator;
use crate::platform; use crate::platform;
use regex::Regex; use regex::Regex;
@@ -34,13 +34,25 @@ pub fn simplify_dir_names<P: AsRef<Path>>(dirs: &[P]) -> HashSet<PathBuf> {
top_level_names top_level_names
} }
pub fn get_filesystem_devices<P: AsRef<Path>>(paths: &[P]) -> HashSet<u64> { pub fn get_filesystem_devices<P: AsRef<Path>>(paths: &[P], follow_links: bool) -> HashSet<u64> {
use std::fs;
// Gets the device ids for the filesystems which are used by the argument paths // Gets the device ids for the filesystems which are used by the argument paths
paths paths
.iter() .iter()
.filter_map(|p| match get_metadata(p, false) { .filter_map(|p| {
Some((_size, Some((_id, dev)), _time)) => Some(dev), let follow_links = if follow_links {
_ => None, // slow path: If dereference-links is set, then we check if the file is a symbolic link
match fs::symlink_metadata(p) {
Ok(metadata) => metadata.file_type().is_symlink(),
Err(_) => false,
}
} else {
false
};
match get_metadata(p, false, follow_links) {
Some((_size, Some((_id, dev)), _time)) => Some(dev),
_ => None,
}
}) })
.collect() .collect()
} }
@@ -65,13 +77,17 @@ pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool
} }
} }
pub fn is_filtered_out_due_to_file_time(filter_time: &(Operater, i64), actual_time: i64) -> bool { pub fn is_filtered_out_due_to_file_time(
filter_time: &Option<(Operator, i64)>,
actual_time: i64,
) -> bool {
match filter_time { match filter_time {
(Operater::Equal, bound_time) => { None => false,
Some((Operator::Equal, bound_time)) => {
!(actual_time >= *bound_time && actual_time < *bound_time + DAY_SECONDS) !(actual_time >= *bound_time && actual_time < *bound_time + DAY_SECONDS)
} }
(Operater::GreaterThan, bound_time) => actual_time < *bound_time, Some((Operator::GreaterThan, bound_time)) => actual_time < *bound_time,
(Operater::LessThan, bound_time) => actual_time > *bound_time, Some((Operator::LessThan, bound_time)) => actual_time > *bound_time,
} }
} }

View File

@@ -2,7 +2,7 @@ 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;
use std::{fs, io, str}; use std::{io, str};
static INIT: Once = Once::new(); static INIT: Once = Once::new();
static UNREADABLE_DIR_PATH: &str = "/tmp/unreadable_dir"; static UNREADABLE_DIR_PATH: &str = "/tmp/unreadable_dir";
@@ -38,6 +38,7 @@ fn copy_test_data(dir: &str) {
fn create_unreadable_directory() -> io::Result<()> { fn create_unreadable_directory() -> io::Result<()> {
#[cfg(unix)] #[cfg(unix)]
{ {
use std::fs;
use std::fs::Permissions; use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
fs::create_dir_all(UNREADABLE_DIR_PATH)?; fs::create_dir_all(UNREADABLE_DIR_PATH)?;

View File

@@ -254,3 +254,10 @@ pub fn test_force_color() {
assert!(output.contains("\x1B[31m")); assert!(output.contains("\x1B[31m"));
assert!(output.contains("\x1B[0m")); assert!(output.contains("\x1B[0m"));
} }
#[test]
pub fn test_collapse() {
let output = build_command(vec!["--collapse", "many", "tests/test_dir/"]);
assert!(output.contains("many"));
assert!(!output.contains("hello_file"));
}