Compare commits

...

54 Commits

Author SHA1 Message Date
andy.boot
e6d433ba59 Increment version 2022-02-26 11:34:24 +00:00
andy.boot
e87534be59 Improve help text 2022-02-26 11:34:24 +00:00
andy.boot
1e27288ec2 Fix test: Remove unwanted mod
This line caused the test_symlinks files to be run twice.
2022-02-26 11:10:26 +00:00
andy.boot
9f4a5daee6 Update library: Clap
Required several changes to main.rs as v3 had breaking changes.
2022-02-26 11:10:26 +00:00
andy.boot
27f0a015ef Update cargo packages 2022-02-26 11:10:26 +00:00
andy.boot
20d89bef91 Neaten code 2022-02-26 09:58:47 +00:00
andy.boot
469e6d0a69 Fix: Bug: names may be shortened unnecessarily
The code calculating the width of a row to use vs the width of the
terminal wasn't quite right.

If we had 2 long filepaths one at the top of the dir tree and one at the
bottom the old code would shorten both equally. This doesn't make sense
as the one at the shallowest part of the tree would have used less
screen real estate and so should show a longer part of the filepath.
2022-02-26 09:58:47 +00:00
andy.boot
2d58609d54 Fix: Add assert for low terminal width
If terminal is not wide enought to print text, then print an error
message

https://github.com/bootandy/dust/issues/204
2022-01-02 22:41:02 +00:00
andy.boot
109a0b90d4 Update help text: number_of_lines
Remove note about 'height' and '-h being help' because it confused
people.

Add text to explain default height is terminal height - 10
https://github.com/bootandy/dust/issues/201
2022-01-02 22:41:02 +00:00
andy.boot
ab67c1a50e Update help text: Add note for -c about watch
Issue was raised about using dust with watch. The solution is to use
watch --color dust or watch dust -c

https://github.com/bootandy/dust/issues/205
2022-01-02 22:41:02 +00:00
Sourajyoti Basak
6a34b52d15 docs(readme): add pacstall installation method 2021-12-21 10:07:28 +00:00
Gustavobb
f708305190 Feature: Implement ISO output 2021-11-20 12:13:30 +00:00
andy.boot
2749f56b7a Tests: Fix test on mac (hack)
For some unknown reason mac takes offence to searching for
'test_dir_unicode'. 'test_dir_hidden' seems to work fine.

This isn't a proper fix as I've just hacked round the problem. Ideally
I'd need a mac user to do some investigation.
2021-11-20 12:05:48 +00:00
andy.boot
d983175189 Cleanup: Vec -> &[]. Remove println. 2021-10-24 14:05:14 +01:00
andy.boot
4b3dc3988d Tests: Move test apparent size to exact_output
Because apparent_size uses the test files created in /tmp it needs to be
in the test_exact_output where those files are copied. Otherwise the files
may not be there when the test is run.
2021-10-24 14:05:14 +01:00
andy.boot
fa4405b58b Cleanup: Remove unused comment
This was fixed a few commits ago
2021-10-24 14:05:14 +01:00
andy.boot
abb08f8e1a Tests: Add tests for multi regex support 2021-10-24 14:05:14 +01:00
andy.boot
9f91d446c1 Feature/Bugfix: Allow multiple regexs to be used
The -e and -v flags allow multiple values. Before values after the first
were ignored. This change allows the user to specify multiple regexs and
have them all applied.
2021-10-24 14:05:14 +01:00
andy.boot
1b07c3c4f3 Update README.md
add note from https://github.com/bootandy/dust/issues/191
2021-10-24 09:33:02 +01:00
andy.boot
e55b917c96 Add .deb files to release procedure 2021-10-11 18:33:27 +01:00
andy.boot
0301c7a058 Increment version
(skipped several due to build difficulties)
2021-09-19 13:50:10 +01:00
andy.boot
bbed8d7478 Revert "Fix ci: Github actions: Publish step"
This reverts commit 5116c1c8a1.
2021-09-19 13:50:10 +01:00
andy.boot
17e7390e25 Increment version
new flags added -e -v -t
2021-09-19 11:40:21 +01:00
andy.boot
1cb731533b bugfix: Allow dust to work on low width terminal
Do not assume the min width is 80 (unless on windows).
2021-09-19 11:40:21 +01:00
andy.boot
d6c2482150 Clap: use default_value on input
This avoids uses one less 'match' statement
2021-09-19 11:12:23 +01:00
andy.boot
9d2e6d2b36 Feature: Filter by invert_filter: reverse match
Mimic grep's -v option.

Allows dust to only match files that do not match the given filter
2021-09-19 11:12:23 +01:00
andy.boot
124c19b5c9 Feature: Adding file types filter & F flag changed
-t = Show summary of types

-e = Filter by regex
	allows you to specify a file type like -e "\.txt$"

Change behaviour of '-f' flag - it now counts only files. Before it
counted files & directories. This was needed for compatibility with
the new '-e' filter flag
2021-09-19 11:12:23 +01:00
Ethan Smith
d8a334df3b Make the other parts of the workflow conditional 2021-09-06 08:54:27 +01:00
Ethan Smith
c485e84145 Use single quotes? 2021-09-06 08:54:27 +01:00
Ethan Smith
f51e9dd222 Conditionally build on musl and fix typo 2021-09-06 08:54:27 +01:00
Ethan Smith
c25f7d342c Build Debian packages in CI 2021-09-06 08:54:27 +01:00
andy.boot
ca0a93f222 Tests: Refactor
Move apparent size test to 'test_flags' file. We can no longer test the
exact output of this test as it changes too much on different filesystems

Move common code to shared initialization function in test_flags.rs
2021-08-06 11:07:48 +01:00
andy.boot
9cf260e42b Tests: Refactor: Improve exact output tests
Move more shared code into single function
2021-08-06 11:07:48 +01:00
andy.boot
87b1f50b39 Tests: Refactor: Neaten 2021-08-06 11:07:48 +01:00
andy.boot
3458c98bd0 Tests: Refactor: More code reuse 2021-08-06 11:07:48 +01:00
andy.boot
2ad420d370 Possible test fix: Some tests fail on some linux
An issue raised that the test output looked a lot like the mac output.
This change causes test to pass if output is either mac or linux style
2021-08-06 10:21:46 +01:00
andy.boot
2047f99c6d Update README.md 2021-08-06 10:11:10 +01:00
andy.boot
9bd2f9fc2a Update README.md 2021-08-06 10:01:10 +01:00
andy.boot
5116c1c8a1 Fix ci: Github actions: Publish step
The rules for github actions appear to have changed
2021-08-05 19:10:48 +01:00
andy.boot
07ffd04950 Increment version
This version includes fix for the -f flag
2021-08-05 08:47:47 +01:00
andy.boot
dfa574375b clippy: Fix clippy lints
New rustup adds more lints
2021-08-05 08:47:47 +01:00
andy.boot
9de2e7d723 bugfix: Fix crash when using '-f' flag
The old code was subtly different in the way the root node worked. This
changed in the v0.6.0 version when dependencies were removed. The code
to handle file count was never updated

https://github.com/bootandy/dust/issues/162
2021-07-29 08:54:32 +01:00
andy.boot
8fddb24165 Increment version 2021-07-19 14:09:21 +01:00
andy.boot
ed3902f07e Rename file: dirwalker -> dir_walker
Felt like this file was missing an '_'
2021-07-16 14:13:12 +01:00
andy.boot
f219a752d6 Refactor: Compress arguemnts to one object
Several arguments were passed around the dirwalker file. Compress them
into a single struct.
2021-07-16 14:13:12 +01:00
andy.boot
f6e36aba52 Feature: Re-introduce -x flag to limit filesystem
-x flag allows dust to limit itself to the current filesystem
2021-07-16 14:13:12 +01:00
andy.boot
c286b8ba97 Revert "README: Remove -x option"
This reverts commit 1d062cf683.
2021-07-16 14:13:12 +01:00
andy.boot
3dad7abfb8 Change size of softlinks to 0
Instead of the size of what they point at
2021-07-16 14:13:12 +01:00
andy.boot
42163abb73 Cargo: Remove num_cpus library. Ran update
Not needed since v0.6.0
Ran cargo update
2021-07-07 18:35:05 +01:00
andy.boot
8e0188c755 README: Update usage examples of dust 2021-07-07 18:35:05 +01:00
andy.boot
555d86206d ci: update versions of ubuntu 2021-06-23 10:05:48 +01:00
andy.boot
02392881c5 Update version 2021-06-23 09:40:58 +01:00
andy.boot
6f1175ef8d README: Add another tool to list of alternatives 2021-06-23 09:40:58 +01:00
andy.boot
1d062cf683 README: Remove -x option
This behaviour was removed in previous commit
2021-06-23 09:40:58 +01:00
16 changed files with 793 additions and 531 deletions

View File

@@ -82,11 +82,11 @@ jobs:
job:
# { os, target, cargo-options, features, use-cross, toolchain }
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , use-cross: use-cross }
- { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-18.04 , target: i686-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-20.04 , target: i686-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-20.04 , target: i686-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-20.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-20.04 , target: x86_64-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-18.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-18.04 , target: x86_64-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-16.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
- { os: macos-latest , target: x86_64-apple-darwin }
- { os: windows-latest , target: i686-pc-windows-gnu }
- { os: windows-latest , target: i686-pc-windows-msvc }
@@ -205,6 +205,18 @@ jobs:
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
command: build
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
- name: Install cargo-deb
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-deb
if: ${{ contains(matrix.job.target, 'musl') }}
- name: Build deb
uses: actions-rs/cargo@v1
with:
command: deb
args: --no-build --target=${{ matrix.job.target }}
if: ${{ contains(matrix.job.target, 'musl') }}
- name: Test
uses: actions-rs/cargo@v1
with:
@@ -216,6 +228,12 @@ jobs:
with:
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
- name: Archive deb artifacts
uses: actions/upload-artifact@master
with:
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}.deb
path: target/${{ matrix.job.target }}/debian
if: ${{ contains(matrix.job.target, 'musl') }}
- name: Package
shell: bash
run: |
@@ -239,6 +257,8 @@ jobs:
with:
files: |
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
target/${{ matrix.job.target }}/debian/*.deb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

266
Cargo.lock generated
View File

@@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
@@ -9,15 +11,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@@ -29,9 +22,9 @@ dependencies = [
[[package]]
name = "assert_cmd"
version = "1.0.5"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88b6bd5df287567ffdf4ddf4d33060048e1068308e5f62d81c6f9824a045a48"
checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe"
dependencies = [
"bstr",
"doc-comment",
@@ -54,21 +47,21 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.0.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
@@ -83,25 +76,25 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.3"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
checksum = "5177fac1ab67102d8989464efd043c6ff44191b1557ec1ddd489b4f7e1447e77"
dependencies = [
"ansi_term 0.11.0",
"atty",
"bitflags",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"term_size",
"termcolor",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -109,9 +102,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if",
"crossbeam-epoch",
@@ -120,9 +113,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -133,19 +126,19 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "difference"
version = "2.0.0"
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "doc-comment"
@@ -155,14 +148,14 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "du-dust"
version = "0.5.4"
version = "0.8.0"
dependencies = [
"ansi_term 0.12.1",
"ansi_term",
"assert_cmd",
"clap",
"lscolors",
"num_cpus",
"rayon",
"regex",
"stfu8",
"tempfile",
"terminal_size",
@@ -178,25 +171,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "getrandom"
version = "0.2.3"
name = "fastrand"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
dependencies = [
"cfg-if",
"libc",
"wasi",
"instant",
]
[[package]]
name = "hermit-abi"
version = "0.1.18"
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -205,9 +230,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.97"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "lscolors"
@@ -215,104 +240,68 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
dependencies = [
"ansi_term 0.12.1",
"ansi_term",
]
[[package]]
name = "memchr"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
version = "0.6.4"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "predicates"
version = "1.0.8"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df"
checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c"
dependencies = [
"difference",
"difflib",
"itertools",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb"
[[package]]
name = "predicates-tree"
version = "1.0.2"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2"
checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032"
dependencies = [
"predicates-core",
"treeline",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
"termtree",
]
[[package]]
@@ -342,9 +331,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
@@ -389,9 +378,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "stfu8"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf70433e3300a3c395d06606a700cdf4205f4f14dbae2c6833127c6bb22db77"
checksum = "019f0c664fd85d5a87dcfb62b40b691055392a35a6e59f4df83d4b770db7e876"
dependencies = [
"lazy_static",
"regex",
@@ -399,32 +388,31 @@ dependencies = [
[[package]]
name = "strsim"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "tempfile"
version = "3.2.0"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"rand",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "term_size"
version = "0.3.2"
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"libc",
"winapi",
"winapi-util",
]
[[package]]
@@ -438,14 +426,16 @@ dependencies = [
]
[[package]]
name = "textwrap"
version = "0.11.0"
name = "termtree"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"term_size",
"unicode-width",
]
checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b"
[[package]]
name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
[[package]]
name = "thousands"
@@ -453,23 +443,11 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
[[package]]
name = "treeline"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]]
name = "unicode-width"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "wait-timeout"
@@ -480,12 +458,6 @@ dependencies = [
"libc",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"

View File

@@ -1,9 +1,10 @@
[package]
name = "du-dust"
description = "A more intuitive version of du"
version = "0.5.4"
version = "0.8.0"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2018"
readme = "README.md"
documentation = "https://github.com/bootandy/dust"
homepage = "https://github.com/bootandy/dust"
@@ -22,14 +23,14 @@ path = "src/main.rs"
[dependencies]
ansi_term = "0.12"
clap = { version = "=2.33", features = ["wrap_help"] }
clap = { version = "=3", features=["cargo"] }
lscolors = "0.7"
num_cpus = "1"
terminal_size = "0.1"
unicode-width = "0.1"
rayon="1"
thousands = "0.2"
stfu8 = "0.2"
regex = "1"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"
@@ -41,3 +42,16 @@ tempfile = "=3"
[[test]]
name = "integration"
path = "tests/tests.rs"
[package.metadata.deb]
section = "utils"
assets = [
["target/release/dust", "usr/bin/", "755"],
["LICENSE", "usr/share/doc/du-dust/", "644"],
["README.md", "usr/share/doc/du-dust/README", "644"],
]
extended-description = """\
Dust is meant to give you an instant overview of which directories are using
disk space without requiring sort or head. Dust will print a maximum of one
'Did not have permissions message'.
"""

View File

@@ -26,6 +26,14 @@ Because I want an easy way to see where my disk is being used.
* `brew tap tgotwig/linux-dust && brew install dust`
#### [Pacstall](https://github.com/pacstall/pacstall) (Debian/Ubuntu)
* `pacstall -I dust-bin`
#### Windows:
* Windows GNU version - works
* Windows MSVC - requires: VCRUNTIME140.dll
#### Download
* Download Linux/Mac binary from [Releases](https://github.com/bootandy/dust/releases)
@@ -38,20 +46,27 @@ Dust is meant to give you an instant overview of which directories are using dis
Dust will list a slightly-less-than-the-terminal-height number of the biggest subdirectories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest subdirectories will be colored.
The different colors on the bars: These represent the combined tree hierarchy & disk usage. The shades of grey are used to indicate which parent folder a subfolder belongs to. For instance, look at the above screenshot. `.steam` is a folder taking 44% of the space. From the `.steam` bar is a light grey line that goes up. All these folders are inside `.steam` so if you delete `.steam` all that stuff will be gone too.
## Usage
```
Usage: dust
Usage: dust <dir>
Usage: dust <dir> <another_dir> <and_more>
Usage: dust -p <dir> (full-path - does not shorten the path of the subdirectories)
Usage: dust -s <dir> (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 <dir> (shows 30 directories instead of the default)
Usage: dust -d 3 <dir> (shows 3 levels of subdirectories)
Usage: dust -r <dir> (reverse order of output, with root at the lowest)
Usage: dust -x <dir> (only show directories on the same filesystem)
Usage: dust -X ignore <dir> (ignore all files and directories with the name 'ignore')
Usage: dust -b <dir> (do not show percentages or draw ASCII bars)
Usage: dust -p (full-path - Show fullpath of the subdirectories)
Usage: dust -s (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 (shows 30 directories instead of the default [default is terminal height])
Usage: dust -d 3 (shows 3 levels of subdirectories)
Usage: dust -r (reverse order of output)
Usage: dust -X ignore (ignore all files and directories with the name 'ignore')
Usage: dust -x (only show directories on the same filesystem)
Usage: dust -b (do not show percentages or draw ASCII bars)
Usage: dust -i (do not show hidden files)
Usage: dust -c (No colors [monochrome])
Usage: dust -f (Count files instead of diskspace)
Usage: dust -t Group by filetype
Usage: dust -e regex Only include files matching this regex (eg dust -e "\.png$" would match png files)
```
@@ -61,6 +76,7 @@ Usage: dust -b <dir> (do not show percentages or draw ASCII bars)
* [dutree](https://github.com/nachoparker/dutree)
* [dua](https://github.com/Byron/dua-cli/)
* [pdu](https://github.com/KSXGitHub/parallel-disk-usage)
* [dirstat-rs](https://github.com/scullionw/dirstat-rs)
* du -d 1 -h | sort -h
Note: Apparent-size is calculated slightly differently in dust to gdu. In dust each hard link is counted as using file_length space. In gdu only the first entry is counted.

View File

@@ -1,6 +1,6 @@
# ----------- To do a release ---------
# edit version in cargo.toml
# tag a commit and push (increment version first):
# tag a commit and push (increment version in Cargo.toml first):
# git tag v0.4.5
# git push origin v0.4.5

View File

@@ -1,8 +1,11 @@
use std::fs;
use crate::node::Node;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use regex::Regex;
use std::path::PathBuf;
use std::sync::atomic;
@@ -13,30 +16,29 @@ use std::collections::HashSet;
use crate::node::build_node;
use std::fs::DirEntry;
pub fn walk_it(
dirs: HashSet<PathBuf>,
ignore_directories: HashSet<PathBuf>,
use_apparent_size: bool,
by_filecount: bool,
ignore_hidden: bool,
) -> (Vec<Node>, bool) {
use crate::platform::get_metadata;
pub struct WalkData<'a> {
pub ignore_directories: HashSet<PathBuf>,
pub filter_regex: &'a [Regex],
pub invert_filter_regex: &'a [Regex],
pub allowed_filesystems: HashSet<u64>,
pub use_apparent_size: bool,
pub by_filecount: bool,
pub ignore_hidden: bool,
}
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> (Vec<Node>, bool) {
let permissions_flag = AtomicBool::new(false);
let top_level_nodes: Vec<_> = dirs
.into_iter()
.filter_map(|d| {
let n = walk(
d,
&permissions_flag,
&ignore_directories,
use_apparent_size,
by_filecount,
ignore_hidden,
);
let n = walk(d, &permissions_flag, &walk_data);
match n {
Some(n) => {
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
clean_inodes(n, &mut inodes, use_apparent_size)
clean_inodes(n, &mut inodes, walk_data.use_apparent_size)
}
None => None,
}
@@ -74,24 +76,39 @@ fn clean_inodes(
});
}
fn ignore_file(
entry: &DirEntry,
ignore_hidden: bool,
ignore_directories: &HashSet<PathBuf>,
) -> 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_ignored_path = ignore_directories.contains(&entry.path());
(is_dot_file && ignore_hidden) || is_ignored_path
let is_ignored_path = walk_data.ignore_directories.contains(&entry.path());
if !walk_data.allowed_filesystems.is_empty() {
let size_inode_device = get_metadata(&entry.path(), false);
if let Some((_size, Some((_id, dev)))) = size_inode_device {
if !walk_data.allowed_filesystems.contains(&dev) {
return true;
}
}
}
// Keeping `walk_data.filter_regex.is_empty()` is important for performance reasons, it stops unnecessary work
if !walk_data.filter_regex.is_empty()
&& entry.path().is_file()
&& is_filtered_out_due_to_regex(walk_data.filter_regex, &entry.path())
{
return true;
}
if !walk_data.invert_filter_regex.is_empty()
&& entry.path().is_file()
&& is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &entry.path())
{
return true;
}
(is_dot_file && walk_data.ignore_hidden) || is_ignored_path
}
fn walk(
dir: PathBuf,
permissions_flag: &AtomicBool,
ignore_directories: &HashSet<PathBuf>,
use_apparent_size: bool,
by_filecount: bool,
ignore_hidden: bool,
) -> Option<Node> {
fn walk(dir: PathBuf, permissions_flag: &AtomicBool, walk_data: &WalkData) -> Option<Node> {
let mut children = vec![];
if let Ok(entries) = fs::read_dir(dir.clone()) {
@@ -104,25 +121,22 @@ fn walk(
// rayon doesn't parallelise as well giving a 3X performance drop
// hence we unravel the recursion a bit
// return walk(entry.path(), permissions_flag, ignore_directories, use_apparent_size, by_filecount, ignore_hidden);
// return walk(entry.path(), permissions_flag, ignore_directories, allowed_filesystems, use_apparent_size, by_filecount, ignore_hidden);
if !ignore_file(&entry, ignore_hidden, &ignore_directories) {
if !ignore_file(entry, walk_data) {
if let Ok(data) = entry.file_type() {
if data.is_dir() && !data.is_symlink() {
return walk(
entry.path(),
permissions_flag,
ignore_directories,
use_apparent_size,
by_filecount,
ignore_hidden,
);
return walk(entry.path(), permissions_flag, walk_data);
}
return build_node(
entry.path(),
vec![],
use_apparent_size,
by_filecount,
walk_data.filter_regex,
walk_data.invert_filter_regex,
walk_data.use_apparent_size,
data.is_symlink(),
data.is_file(),
walk_data.by_filecount,
);
}
}
@@ -135,7 +149,16 @@ fn walk(
} else {
permissions_flag.store(true, atomic::Ordering::Relaxed);
}
build_node(dir, children, use_apparent_size, by_filecount)
build_node(
dir,
children,
walk_data.filter_regex,
walk_data.invert_filter_regex,
walk_data.use_apparent_size,
false,
false,
walk_data.by_filecount,
)
}
mod tests {

View File

@@ -28,6 +28,7 @@ pub struct DisplayData {
pub base_size: u64,
pub longest_string_length: usize,
pub ls_colors: LsColors,
pub iso: bool,
}
impl DisplayData {
@@ -107,39 +108,36 @@ impl DrawData<'_> {
#[allow(clippy::too_many_arguments)]
pub fn draw_it(
permission_error: bool,
use_full_path: bool,
is_reversed: bool,
no_colors: bool,
no_percents: bool,
terminal_width: usize,
by_filecount: bool,
option_root_node: Option<DisplayNode>,
root_node: DisplayNode,
iso: bool,
) {
if permission_error {
eprintln!("Did not have permissions for all directories");
}
if option_root_node.is_none() {
return;
}
let root_node = option_root_node.unwrap();
let num_chars_needed_on_left_most = if by_filecount {
let max_size = root_node.children.iter().map(|n| n.size).fold(0, max);
let max_size = root_node.size;
max_size.separate_with_commas().chars().count()
} else {
5 // Under normal usage we need 5 chars to display the size of a directory
};
let terminal_width = terminal_width - 9 - num_chars_needed_on_left_most;
assert!(
terminal_width > num_chars_needed_on_left_most + 2,
"Not enough terminal width"
);
let allowed_width = terminal_width - num_chars_needed_on_left_most - 2;
let num_indent_chars = 3;
let longest_string_length =
find_longest_dir_name(&root_node, num_indent_chars, terminal_width, !use_full_path);
find_longest_dir_name(&root_node, num_indent_chars, allowed_width, !use_full_path);
let max_bar_length = if no_percents || longest_string_length >= terminal_width as usize {
let max_bar_length = if no_percents || longest_string_length + 7 >= allowed_width as usize {
0
} else {
terminal_width as usize - longest_string_length
allowed_width as usize - longest_string_length - 7
};
let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect::<String>();
@@ -153,6 +151,7 @@ pub fn draw_it(
base_size: root_node.size,
longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(),
iso,
};
let draw_data = DrawData {
indent: "".to_string(),
@@ -261,23 +260,30 @@ fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &Display
let indent_and_name = format!("{} {}", indent, name);
let width = UnicodeWidthStr::width(&*indent_and_name);
assert!(display_data.longest_string_length >= width);
assert!(
display_data.longest_string_length >= width,
"Terminal width not wide enough to draw directory tree"
);
// Add spaces after the filename so we can draw the % used bar chart.
let name_and_padding = name
+ &(repeat(" ")
.take(display_data.longest_string_length - width)
.collect::<String>());
+ " "
.repeat(display_data.longest_string_length - width)
.as_str();
maybe_trim_filename(name_and_padding, display_data)
name_and_padding
}
fn maybe_trim_filename(name_in: String, display_data: &DisplayData) -> String {
if UnicodeWidthStr::width(&*name_in) > display_data.longest_string_length {
let name = name_in
.chars()
.take(display_data.longest_string_length - 2)
.collect::<String>();
fn maybe_trim_filename(name_in: String, indent: &str, display_data: &DisplayData) -> String {
let indent_length = UnicodeWidthStr::width(indent);
assert!(
display_data.longest_string_length >= indent_length + 2,
"Terminal width not wide enough to draw directory tree"
);
let max_size = display_data.longest_string_length - indent_length;
if UnicodeWidthStr::width(&*name_in) > max_size {
let name = name_in.chars().take(max_size - 2).collect::<String>();
name + ".."
} else {
name_in
@@ -310,7 +316,7 @@ fn get_name_percent(
(percents, name_and_padding)
} else {
let n = get_printable_name(&node.name, display_data.short_paths);
let name = maybe_trim_filename(n, display_data);
let name = maybe_trim_filename(n, indent, display_data);
("".into(), name)
}
}
@@ -320,9 +326,9 @@ fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayD
let size_as_str = node.size.separate_with_commas();
let spaces_to_add =
display_data.num_chars_needed_on_left_most - size_as_str.chars().count();
size_as_str + &*repeat(' ').take(spaces_to_add).collect::<String>()
size_as_str + " ".repeat(spaces_to_add).as_str()
} else {
format!("{:>5}", human_readable_number(node.size))
format!("{:>5}", human_readable_number(node.size, display_data.iso))
};
if is_biggest && display_data.colors_on {
@@ -351,9 +357,10 @@ fn get_pretty_name(
}
}
fn human_readable_number(size: u64) -> String {
fn human_readable_number(size: u64, iso: bool) -> String {
for (i, u) in UNITS.iter().enumerate() {
let marker = 1024u64.pow((UNITS.len() - i) as u32);
let num: u64 = if iso { 1000 } else { 1024 };
let marker = num.pow((UNITS.len() - i) as u32);
if size >= marker {
if size / marker < 10 {
return format!("{:.1}{}", (size as f32 / marker as f32), u);
@@ -382,6 +389,7 @@ mod tests {
base_size: 1,
longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(),
iso: false,
}
}
@@ -401,7 +409,7 @@ mod tests {
indent,
percent_bar,
is_biggest,
&get_fake_display_data(6),
&get_fake_display_data(20),
);
assert_eq!(s, " 4.0K ┌─┴ short");
}
@@ -422,21 +430,27 @@ mod tests {
let s = format_string(&n, indent, percent_bar, is_biggest, &dd);
assert_eq!(
s,
" 4.0K ┌─┴ very_long_name_longer_than_the_eighty_character_limit_very_lon.."
" 4.0K ┌─┴ very_long_name_longer_than_the_eighty_character_limit_very_.."
);
}
#[test]
fn test_human_readable_number() {
assert_eq!(human_readable_number(1), "1B");
assert_eq!(human_readable_number(956), "956B");
assert_eq!(human_readable_number(1004), "1004B");
assert_eq!(human_readable_number(1024), "1.0K");
assert_eq!(human_readable_number(1536), "1.5K");
assert_eq!(human_readable_number(1024 * 512), "512K");
assert_eq!(human_readable_number(1024 * 1024), "1.0M");
assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M");
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G");
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T");
assert_eq!(human_readable_number(1, false), "1B");
assert_eq!(human_readable_number(956, false), "956B");
assert_eq!(human_readable_number(1004, false), "1004B");
assert_eq!(human_readable_number(1024, false), "1.0K");
assert_eq!(human_readable_number(1536, false), "1.5K");
assert_eq!(human_readable_number(1024 * 512, false), "512K");
assert_eq!(human_readable_number(1024 * 1024, false), "1.0M");
assert_eq!(
human_readable_number(1024 * 1024 * 1024 - 1, false),
"1023M"
);
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20, false), "20G");
assert_eq!(
human_readable_number(1024 * 1024 * 1024 * 1024, false),
"1.0T"
);
}
}

View File

@@ -36,11 +36,12 @@ impl DisplayNode {
}
pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = DisplayNode> {
if is_reversed {
let children: Vec<DisplayNode> = self.children.clone().into_iter().rev().collect();
children.into_iter()
// we box to avoid the clippy lint warning
let out: Box<dyn Iterator<Item = DisplayNode>> = if is_reversed {
Box::new(self.children.clone().into_iter().rev())
} else {
self.children.clone().into_iter()
}
Box::new(self.children.clone().into_iter())
};
out
}
}

View File

@@ -1,6 +1,7 @@
use crate::display_node::DisplayNode;
use crate::node::Node;
use std::collections::BinaryHeap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
@@ -13,7 +14,11 @@ pub fn get_by_depth(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode>
Some(build_by_depth(&root, n - 1))
}
pub fn get_biggest(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode> {
pub fn get_biggest(
top_level_nodes: Vec<Node>,
n: usize,
using_a_filter: bool,
) -> Option<DisplayNode> {
if top_level_nodes.is_empty() {
// perhaps change this, bring back Error object?
return None;
@@ -22,18 +27,17 @@ pub fn get_biggest(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode>
let mut heap = BinaryHeap::new();
let number_top_level_nodes = top_level_nodes.len();
let root = get_new_root(top_level_nodes);
root.children.iter().for_each(|c| heap.push(c));
let mut allowed_nodes = HashSet::new();
allowed_nodes.insert(&root.name);
heap = add_children(using_a_filter, &root, heap);
for _ in number_top_level_nodes..n {
let line = heap.pop();
match line {
Some(line) => {
line.children.iter().for_each(|c| heap.push(c));
allowed_nodes.insert(&line.name);
heap = add_children(using_a_filter, line, heap);
}
None => break,
}
@@ -41,6 +45,72 @@ pub fn get_biggest(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode>
recursive_rebuilder(&allowed_nodes, &root)
}
pub fn get_all_file_types(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode> {
let mut map: HashMap<String, DisplayNode> = HashMap::new();
build_by_all_file_types(top_level_nodes, &mut map);
let mut by_types: Vec<DisplayNode> = map.into_iter().map(|(_k, v)| v).collect();
by_types.sort();
by_types.reverse();
let displayed = if by_types.len() <= n {
by_types
} else {
let (displayed, rest) = by_types.split_at(if n > 1 { n - 1 } else { 1 });
let remaining = DisplayNode {
name: PathBuf::from("(others)"),
size: rest.iter().map(|a| a.size).sum(),
children: vec![],
};
let mut displayed = displayed.to_vec();
displayed.push(remaining);
displayed
};
let result = DisplayNode {
name: PathBuf::from("(total)"),
size: displayed.iter().map(|a| a.size).sum(),
children: displayed,
};
Some(result)
}
fn add_children<'a>(
using_a_filter: bool,
line: &'a Node,
mut heap: BinaryHeap<&'a Node>,
) -> BinaryHeap<&'a Node> {
if using_a_filter {
line.children.iter().for_each(|c| {
if c.name.is_file() || c.size > 0 {
heap.push(c)
}
});
} else {
line.children.iter().for_each(|c| heap.push(c));
}
heap
}
fn build_by_all_file_types(top_level_nodes: Vec<Node>, counter: &mut HashMap<String, DisplayNode>) {
for node in top_level_nodes {
if node.name.is_file() {
let ext = node.name.extension();
let key: String = match ext {
Some(e) => ".".to_string() + &e.to_string_lossy(),
None => "(no extension)".into(),
};
let mut display_node = counter.entry(key.clone()).or_insert(DisplayNode {
name: PathBuf::from(key),
size: 0,
children: vec![],
});
display_node.size += node.size;
}
build_by_all_file_types(node.children, counter)
}
}
fn build_by_depth(node: &Node, depth: usize) -> DisplayNode {
let new_children = {
if depth == 0 {

View File

@@ -1,20 +1,24 @@
#[macro_use]
extern crate clap;
extern crate rayon;
extern crate regex;
extern crate unicode_width;
use std::collections::HashSet;
use std::process;
use self::display::draw_it;
use clap::{App, AppSettings, Arg};
use dirwalker::walk_it;
use filter::{get_biggest, get_by_depth};
use clap::{crate_version, Arg};
use clap::{Command, Values};
use dir_walker::{walk_it, WalkData};
use filter::{get_all_file_types, get_biggest, get_by_depth};
use regex::Regex;
use std::cmp::max;
use std::path::PathBuf;
use terminal_size::{terminal_size, Height, Width};
use utils::get_filesystem_devices;
use utils::simplify_dir_names;
mod dirwalker;
mod dir_walker;
mod display;
mod display_node;
mod filter;
@@ -59,6 +63,7 @@ fn get_height_of_terminal() -> usize {
}
}
#[cfg(windows)]
fn get_width_of_terminal() -> usize {
// Windows CI runners detect a very low terminal width
if let Some((Width(w), Height(_h))) = terminal_size() {
@@ -68,100 +73,170 @@ fn get_width_of_terminal() -> usize {
}
}
#[cfg(not(windows))]
fn get_width_of_terminal() -> usize {
if let Some((Width(w), Height(_h))) = terminal_size() {
w as usize
} else {
DEFAULT_TERMINAL_WIDTH
}
}
fn get_regex_value(maybe_value: Option<Values>) -> Vec<Regex> {
let mut result = vec![];
if let Some(v) = maybe_value {
for reg in v {
match Regex::new(reg) {
Ok(r) => result.push(r),
Err(e) => {
eprintln!("Ignoring bad value for regex {:?}", e);
process::exit(1);
}
}
}
}
result
}
fn main() {
let default_height = get_height_of_terminal();
let def_num_str = default_height.to_string();
let options = App::new("Dust")
let options = Command::new("Dust")
.about("Like du but more intuitive")
.version(crate_version!())
.setting(AppSettings::TrailingVarArg)
.trailing_var_arg(true)
.arg(
Arg::with_name("depth")
.short("d")
Arg::new("depth")
.short('d')
.long("depth")
.help("Depth to show")
.takes_value(true)
.conflicts_with("number_of_lines"),
)
.arg(
Arg::with_name("number_of_lines")
.short("n")
Arg::new("number_of_lines")
.short('n')
.long("number-of-lines")
.help("Number of lines of output to show. This is Height, (but h is help)")
.help("Number of lines of output to show. (Default is terminal_height - 10)")
.takes_value(true)
.default_value(def_num_str.as_ref()),
)
.arg(
Arg::with_name("display_full_paths")
.short("p")
Arg::new("display_full_paths")
.short('p')
.long("full-paths")
.help("Subdirectories will not have their path shortened"),
)
.arg(
Arg::with_name("ignore_directory")
.short("X")
Arg::new("ignore_directory")
.short('X')
.long("ignore-directory")
.takes_value(true)
.number_of_values(1)
.multiple(true)
.multiple_occurrences(true)
.help("Exclude any file or directory with this name"),
)
.arg(
Arg::with_name("display_apparent_size")
.short("s")
Arg::new("limit_filesystem")
.short('x')
.long("limit-filesystem")
.help("Only count the files and directories on the same filesystem as the supplied directory"),
)
.arg(
Arg::new("display_apparent_size")
.short('s')
.long("apparent-size")
.help("Use file length instead of blocks"),
)
.arg(
Arg::with_name("reverse")
.short("r")
Arg::new("reverse")
.short('r')
.long("reverse")
.help("Print tree upside down (biggest highest)"),
)
.arg(
Arg::with_name("no_colors")
.short("c")
Arg::new("no_colors")
.short('c')
.long("no-colors")
.help("No colors will be printed (normally largest directories are colored)"),
.help("No colors will be printed (Useful for commands like: watch)"),
)
.arg(
Arg::with_name("no_bars")
.short("b")
Arg::new("no_bars")
.short('b')
.long("no-percent-bars")
.help("No percent bars or percentages will be displayed"),
)
.arg(
Arg::with_name("by_filecount")
.short("f")
Arg::new("by_filecount")
.short('f')
.long("filecount")
.help("Directory 'size' is number of child files/dirs not disk size"),
)
.arg(
Arg::with_name("ignore_hidden")
.short("i") // Do not use 'h' this is used by 'help'
Arg::new("ignore_hidden")
.short('i') // Do not use 'h' this is used by 'help'
.long("ignore_hidden")
.help("Obey .git_ignore rules & Do not display hidden files"),
.help("Do not display hidden files"),
)
.arg(
Arg::with_name("width")
.short("w")
Arg::new("invert_filter")
.short('v')
.long("invert-filter")
.takes_value(true)
.number_of_values(1)
.multiple_occurrences(true)
.conflicts_with("filter")
.conflicts_with("types")
.conflicts_with("depth")
.help("Exclude filepaths matching this regex. To ignore png files type: -v \"\\.png$\" "),
)
.arg(
Arg::new("filter")
.short('e')
.long("filter")
.takes_value(true)
.number_of_values(1)
.multiple_occurrences(true)
.conflicts_with("types")
.conflicts_with("depth")
.help("Only include filepaths matching this regex. For png files type: -e \"\\.png$\" "),
)
.arg(
Arg::new("types")
.short('t')
.long("file_types")
.conflicts_with("depth")
.help("show only these file types"),
)
.arg(
Arg::new("width")
.short('w')
.long("terminal_width")
.takes_value(true)
.number_of_values(1)
.help("Specify width of output overriding the auto detection of terminal width"),
)
.arg(Arg::with_name("inputs").multiple(true))
.arg(Arg::new("inputs").multiple_occurrences(true).default_value("."))
.arg(
Arg::new("iso")
.short('H')
.long("si")
.help("print sizes in powers of 1000 (e.g., 1.1G)")
)
.get_matches();
let target_dirs = {
match options.values_of("inputs") {
None => vec!["."],
Some(r) => r.collect(),
}
};
let target_dirs = options
.values_of("inputs")
.expect("Should be a default value here")
.collect();
let number_of_lines = match value_t!(options.value_of("number_of_lines"), usize) {
let summarize_file_types = options.is_present("types");
let filter_regexs = get_regex_value(options.values_of("filter"));
let invert_filter_regexs = get_regex_value(options.values_of("invert_filter"));
let number_of_lines = match options.value_of_t("number_of_lines") {
Ok(v) => v,
Err(_) => {
eprintln!("Ignoring bad value for number_of_lines");
@@ -169,7 +244,7 @@ fn main() {
}
};
let terminal_width = match value_t!(options.value_of("width"), usize) {
let terminal_width = match options.value_of_t("width") {
Ok(v) => v,
Err(_) => get_width_of_terminal(),
};
@@ -191,37 +266,61 @@ fn main() {
let by_filecount = options.is_present("by_filecount");
let ignore_hidden = options.is_present("ignore_hidden");
let limit_filesystem = options.is_present("limit_filesystem");
let simplified_dirs = simplify_dir_names(target_dirs);
let allowed_filesystems = {
if limit_filesystem {
get_filesystem_devices(simplified_dirs.iter())
} else {
HashSet::new()
}
};
let ignored_full_path: HashSet<PathBuf> = ignore_directories
.into_iter()
.flat_map(|x| simplified_dirs.iter().map(move |d| d.join(x.clone())))
.collect();
let (nodes, errors) = walk_it(
simplified_dirs,
ignored_full_path,
let walk_data = WalkData {
ignore_directories: ignored_full_path,
filter_regex: &filter_regexs,
invert_filter_regex: &invert_filter_regexs,
allowed_filesystems,
use_apparent_size,
by_filecount,
ignore_hidden,
);
};
let (top_level_nodes, has_errors) = walk_it(simplified_dirs, walk_data);
let tree = {
match depth {
None => get_biggest(nodes, number_of_lines),
Some(depth) => get_by_depth(nodes, depth),
match (depth, summarize_file_types) {
(_, true) => get_all_file_types(top_level_nodes, number_of_lines),
(Some(depth), _) => get_by_depth(top_level_nodes, depth),
(_, _) => get_biggest(
top_level_nodes,
number_of_lines,
options.values_of("filter").is_some()
|| options.value_of("invert_filter").is_some(),
),
}
};
draw_it(
errors,
options.is_present("display_full_paths"),
!options.is_present("reverse"),
no_colors,
options.is_present("no_bars"),
terminal_width,
by_filecount,
tree,
);
if has_errors {
eprintln!("Did not have permissions for all directories");
}
match tree {
None => {}
Some(root_node) => draw_it(
options.is_present("display_full_paths"),
!options.is_present("reverse"),
no_colors,
options.is_present("no_bars"),
terminal_width,
by_filecount,
root_node,
options.is_present("iso"),
),
}
}

View File

@@ -1,5 +1,8 @@
use crate::platform::get_metadata;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;
use regex::Regex;
use std::cmp::Ordering;
use std::path::PathBuf;
@@ -11,15 +14,37 @@ pub struct Node {
pub inode_device: Option<(u64, u64)>,
}
#[allow(clippy::too_many_arguments)]
pub fn build_node(
dir: PathBuf,
children: Vec<Node>,
filter_regex: &[Regex],
invert_filter_regex: &[Regex],
use_apparent_size: bool,
is_symlink: bool,
is_file: bool,
by_filecount: bool,
) -> Option<Node> {
match get_metadata(&dir, use_apparent_size) {
Some(data) => {
let (size, inode_device) = if by_filecount { (1, data.1) } else { data };
let inode_device = if is_symlink && !use_apparent_size {
None
} else {
data.1
};
let size = if is_filtered_out_due_to_regex(filter_regex, &dir)
|| is_filtered_out_due_to_invert_regex(invert_filter_regex, &dir)
|| (is_symlink && !use_apparent_size)
|| by_filecount && !is_file
{
0
} else if by_filecount {
1
} else {
data.0
};
Some(Node {
name: dir,
size,

View File

@@ -120,9 +120,9 @@ pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(
{
Some((md.len(), None))
} else {
get_metadata_expensive(&d)
get_metadata_expensive(d)
}
}
_ => get_metadata_expensive(&d),
_ => get_metadata_expensive(d),
}
}

View File

@@ -1,11 +1,9 @@
use platform::get_metadata;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
let parent = parent.as_ref();
let child = child.as_ref();
child.starts_with(parent) && !parent.starts_with(child)
}
use crate::platform;
use regex::Regex;
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
let mut top_level_names: HashSet<PathBuf> = HashSet::with_capacity(filenames.len());
@@ -33,6 +31,22 @@ pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf>
top_level_names
}
pub fn get_filesystem_devices<'a, P: IntoIterator<Item = &'a PathBuf>>(paths: P) -> HashSet<u64> {
// Gets the device ids for the filesystems which are used by the argument paths
paths
.into_iter()
.filter_map(|p| {
let meta = get_metadata(p, false);
if let Some((_size, Some((_id, dev)))) = meta {
Some(dev)
} else {
None
}
})
.collect()
}
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
// normalize path ...
// 1. removing repeated separators
@@ -43,6 +57,28 @@ pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
path.as_ref().components().collect::<PathBuf>()
}
pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool {
if filter_regex.is_empty() {
false
} else {
filter_regex
.iter()
.all(|f| !f.is_match(&dir.as_os_str().to_string_lossy()))
}
}
pub fn is_filtered_out_due_to_invert_regex(filter_regex: &[Regex], dir: &Path) -> bool {
filter_regex
.iter()
.any(|f| f.is_match(&dir.as_os_str().to_string_lossy()))
}
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
let parent = parent.as_ref();
let child = child.as_ref();
child.starts_with(parent) && !parent.starts_with(child)
}
mod tests {
#[allow(unused_imports)]
use super::*;

View File

@@ -1,11 +1,10 @@
use assert_cmd::Command;
use std::ffi::OsStr;
use std::str;
use std::sync::Once;
static INIT: Once = Once::new();
mod tests_symlinks;
/**
* This file contains tests that verify the exact output of the command.
* This output differs on Linux / Mac so the tests are harder to write and debug
@@ -37,7 +36,7 @@ fn copy_test_data(dir: &str) {
};
}
pub fn initialize() {
fn initialize() {
INIT.call_once(|| {
copy_test_data("tests/test_dir");
copy_test_data("tests/test_dir2");
@@ -45,161 +44,102 @@ pub fn initialize() {
});
}
fn exact_output_test<T: AsRef<OsStr>>(valid_outputs: Vec<String>, command_args: Vec<T>) {
initialize();
let mut a = &mut Command::cargo_bin("dust").unwrap();
for p in command_args {
a = a.arg(p);
}
let output: String = str::from_utf8(&a.unwrap().stdout).unwrap().into();
assert!(valid_outputs
.iter()
.fold(false, |sum, i| sum || output.contains(i)));
}
// "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_basic() {
// -c is no color mode - This makes testing much simpler
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd.arg("-c").arg("/tmp/test_dir/").unwrap().stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output()));
exact_output_test(main_output(), vec!["-c", "/tmp/test_dir/"])
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_multi_arg() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd
.arg("-c")
.arg("/tmp/test_dir/many/")
.arg("/tmp/test_dir")
.arg("/tmp/test_dir")
.unwrap()
.stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output()));
let command_args = vec![
"-c",
"/tmp/test_dir/many/",
"/tmp/test_dir",
"/tmp/test_dir",
];
exact_output_test(main_output(), command_args);
}
#[cfg(target_os = "macos")]
fn main_output() -> String {
r#"
fn main_output() -> Vec<String> {
// Some linux currently thought to be Manjaro, Arch
// Although probably depends on how drive is formatted
let mac_and_some_linux = r#"
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ many │████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
.to_string();
#[cfg(target_os = "linux")]
fn main_output() -> String {
r#"
let ubuntu = r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33%
8.0K ┌─┴ many │ █████████████████████████████████ │ 67%
12K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
.to_string();
#[cfg(target_os = "windows")]
fn main_output() -> String {
"windows results vary by host".to_string()
vec![mac_and_some_linux, ubuntu]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_long_paths() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd
.arg("-c")
.arg("-p")
.arg("/tmp/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output_long_paths()));
let command_args = vec!["-c", "-p", "/tmp/test_dir/"];
exact_output_test(main_output_long_paths(), command_args);
}
#[cfg(target_os = "macos")]
fn main_output_long_paths() -> String {
r#"
fn main_output_long_paths() -> Vec<String> {
let mac_and_some_linux = r#"
0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── /tmp/test_dir/many/hello_file│█████████████████████████████ │ 100%
4.0K ┌─┴ /tmp/test_dir/many │█████████████████████████████ │ 100%
4.0K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "linux")]
fn main_output_long_paths() -> String {
r#"
.to_string();
let ubuntu = r#"
0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░██████████ │ 33%
8.0K ┌─┴ /tmp/test_dir/many │ ████████████████████ │ 67%
12K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "windows")]
fn main_output_long_paths() -> String {
"windows results vary by host".to_string()
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_apparent_size() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd.arg("-c").arg("-s").arg("/tmp/test_dir").unwrap().stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&output_apparent_size()));
}
#[cfg(target_os = "linux")]
fn output_apparent_size() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ┌─┴ many │ █████████████████████████ │ 50%
8.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "macos")]
fn output_apparent_size() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░░░██ │ 3%
134B ┌─┴ many │ ████████████████████████████ │ 58%
230B ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "windows")]
fn output_apparent_size() -> String {
"windows results vary by host".to_string()
.to_string();
vec![mac_and_some_linux, ubuntu]
}
// Check against directories and files whos names are substrings of each other
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_substring_of_names_and_long_names() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-c").arg("/tmp/test_dir2").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(&no_substring_of_names_output()));
let command_args = vec!["-c", "/tmp/test_dir2"];
exact_output_test(no_substring_of_names_output(), command_args);
}
#[cfg(target_os = "linux")]
fn no_substring_of_names_output() -> String {
"
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
fn no_substring_of_names_output() -> Vec<String> {
let ubuntu = "
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goe..
4.0K ├── dir_name_clash
4.0K │ ┌── hello
8.0K ├─┴ dir
@@ -208,13 +148,10 @@ fn no_substring_of_names_output() -> String {
24K ┌─┴ test_dir2
"
.trim()
.into()
}
.into();
#[cfg(target_os = "macos")]
fn no_substring_of_names_output() -> String {
"
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
let mac_and_some_linux = "
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goe..
4.0K │ ┌── hello
4.0K ├─┴ dir
4.0K ├── dir_name_clash
@@ -223,48 +160,52 @@ fn no_substring_of_names_output() -> String {
12K ┌─┴ test_dir2
"
.trim()
.into()
}
#[cfg(target_os = "windows")]
fn no_substring_of_names_output() -> String {
"PRs".into()
.into();
vec![mac_and_some_linux, ubuntu]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_unicode_directories() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-c").arg("/tmp/test_dir_unicode").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(&unicode_dir()));
let command_args = vec!["-c", "/tmp/test_dir_unicode"];
exact_output_test(unicode_dir(), command_args);
}
#[cfg(target_os = "linux")]
fn unicode_dir() -> String {
fn unicode_dir() -> Vec<String> {
// The way unicode & asian characters are rendered on the terminal should make this line up
"
let ubuntu = "
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
0B ├── 👩.unicode │ █ │ 0%
4.0K ┌─┴ test_dir_unicode │██████████████████████████████████ │ 100%
"
.trim()
.into()
}
.into();
#[cfg(target_os = "macos")]
fn unicode_dir() -> String {
"
let mac_and_some_linux = "
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
0B ├── 👩.unicode │ █ │ 0%
0B ┌─┴ test_dir_unicode │ █ │ 0%
"
.trim()
.into()
.into();
vec![mac_and_some_linux, ubuntu]
}
#[cfg(target_os = "windows")]
fn unicode_dir() -> String {
"".into()
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_apparent_size() {
let command_args = vec!["-c", "-s", "-b", "/tmp/test_dir"];
exact_output_test(apparent_size_output(), command_args);
}
fn apparent_size_output() -> Vec<String> {
// The apparent directory sizes are too unpredictable and system dependant to try and match
let files = r#"
0B ┌── a_file
6B ├── hello_file
"#
.trim()
.to_string();
vec![files]
}

View File

@@ -1,17 +1,25 @@
use assert_cmd::Command;
use std::ffi::OsStr;
use std::str;
/**
* This file contains tests that test a substring of the output using '.contains'
*
* These tests should be the same cross platform
*/
fn build_command<T: AsRef<OsStr>>(command_args: Vec<T>) -> String {
let mut a = &mut Command::cargo_bin("dust").unwrap();
for p in command_args {
a = a.arg(p);
}
str::from_utf8(&a.unwrap().stdout).unwrap().into()
}
// We can at least test the file names are there
#[test]
pub fn test_basic_output() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("tests/test_dir/").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["tests/test_dir/"]);
assert!(output.contains(" ┌─┴ "));
assert!(output.contains("test_dir "));
@@ -25,9 +33,7 @@ pub fn test_basic_output() {
#[test]
pub fn test_output_no_bars_means_no_excess_spaces() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-b").arg("tests/test_dir/").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-b", "tests/test_dir/"]);
// If bars are not being shown we don't need to pad the output with spaces
assert!(output.contains("many"));
assert!(!output.contains("many "));
@@ -35,15 +41,7 @@ pub fn test_output_no_bars_means_no_excess_spaces() {
#[test]
pub fn test_reverse_flag() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-r")
.arg("tests/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-r", "-c", "tests/test_dir/"]);
assert!(output.contains(" └─┬ test_dir "));
assert!(output.contains(" └─┬ many "));
assert!(output.contains(" ├── hello_file"));
@@ -53,15 +51,7 @@ pub fn test_reverse_flag() {
#[test]
pub fn test_d_flag_works() {
// We should see the top level directory but not the sub dirs / files:
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-d")
.arg("1")
.arg("-s")
.arg("tests/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-d", "1", "tests/test_dir/"]);
assert!(!output.contains("hello_file"));
}
@@ -69,31 +59,14 @@ pub fn test_d_flag_works() {
pub fn test_d_flag_works_and_still_recurses_down() {
// We had a bug where running with '-d 1' would stop at the first directory and the code
// would fail to recurse down
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-d")
.arg("1")
.arg("-f")
.arg("-c")
.arg("tests/test_dir2/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains("7 ┌─┴ test_dir2"));
let output = build_command(vec!["-d", "1", "-f", "-c", "tests/test_dir2/"]);
assert!(output.contains("4 ┌─┴ test_dir2"));
}
// Check against directories and files whos names are substrings of each other
#[test]
pub fn test_ignore_dir() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-X")
.arg("dir_substring")
.arg("tests/test_dir2")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-c", "-X", "dir_substring", "tests/test_dir2/"]);
assert!(!output.contains("dir_substring"));
}
@@ -108,25 +81,12 @@ pub fn test_with_bad_param() {
#[test]
pub fn test_hidden_flag() {
// Check we can see the hidden file normally
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("tests/test_dir_hidden_entries")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-c", "tests/test_dir_hidden_entries/"]);
assert!(output.contains(".hidden_file"));
assert!(output.contains("┌─┴ test_dir_hidden_entries"));
// Check that adding the '-h' flag causes us to not see hidden files
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-i")
.arg("tests/test_dir_hidden_entries")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-c", "-i", "tests/test_dir_hidden_entries/"]);
assert!(!output.contains(".hidden_file"));
assert!(output.contains("┌── test_dir_hidden_entries"));
}
@@ -134,16 +94,87 @@ pub fn test_hidden_flag() {
#[test]
pub fn test_number_of_files() {
// Check we can see the hidden file normally
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-f")
.arg("tests/test_dir")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-c", "-f", "tests/test_dir"]);
assert!(output.contains("1 ┌── a_file "));
assert!(output.contains("1 ├── hello_file"));
assert!(output.contains("3 ┌─┴ many"));
assert!(output.contains("4 ┌─┴ test_dir"));
assert!(output.contains("2 ┌─┴ many"));
assert!(output.contains("2 ┌─┴ test_dir"));
}
#[test]
pub fn test_show_files_by_type() {
// Check we can list files by type
let output = build_command(vec!["-c", "-t", "tests"]);
assert!(output.contains(" .unicode"));
assert!(output.contains(" .japan"));
assert!(output.contains(" .rs"));
assert!(output.contains(" (no extension)"));
assert!(output.contains("┌─┴ (total)"));
}
#[test]
pub fn test_show_files_by_regex_match_lots() {
// Check we can see '.rs' files in the tests directory
let output = build_command(vec!["-c", "-e", "\\.rs$", "tests"]);
assert!(output.contains(" ┌─┴ tests"));
assert!(!output.contains("0B ┌── tests"));
assert!(!output.contains("0B ┌─┴ tests"));
}
#[test]
pub fn test_show_files_by_regex_match_nothing() {
// Check there are no files named: '.match_nothing' in the tests directory
let output = build_command(vec!["-c", "-e", "match_nothing$", "tests"]);
assert!(output.contains("0B ┌── tests"));
}
#[test]
pub fn test_show_files_by_regex_match_multiple() {
let output = build_command(vec![
"-c",
"-e",
"test_dir_hidden",
"-e",
"test_dir2",
"-n",
"100",
"tests",
]);
assert!(output.contains("test_dir2"));
assert!(output.contains("test_dir_hidden"));
assert!(!output.contains("many")); // We do not find the 'many' folder in the 'test_dir' folder
}
#[test]
pub fn test_show_files_by_invert_regex() {
let output = build_command(vec!["-c", "-f", "-v", "e", "tests/test_dir2"]);
// There are 0 files without 'e' in the name
assert!(output.contains("0 ┌── test_dir2"));
let output = build_command(vec!["-c", "-f", "-v", "a", "tests/test_dir2"]);
// There are 2 files without 'a' in the name
assert!(output.contains("2 ┌─┴ test_dir2"));
// There are 4 files in the test_dir2 hierarchy
let output = build_command(vec!["-c", "-f", "-v", "match_nothing$", "tests/test_dir2"]);
assert!(output.contains("4 ┌─┴ test_dir2"));
}
#[test]
pub fn test_show_files_by_invert_regex_match_multiple() {
// We ignore test_dir2 & test_dir_unicode, leaving the test_dir folder
// which has the 'many' folder inside
let output = build_command(vec![
"-c",
"-v",
"test_dir2",
"-v",
"test_dir_unicode",
"-n",
"100",
"tests",
]);
assert!(!output.contains("test_dir2"));
assert!(!output.contains("test_dir_unicode"));
assert!(output.contains("many"));
}

View File

@@ -24,7 +24,7 @@ fn get_width_of_terminal() -> u16 {
// Mac test runners create tmp files with very long names, hence it may be shortened in the output
fn get_file_name(name: String) -> String {
let terminal_plus_buffer = (get_width_of_terminal() - 14) as usize;
let terminal_plus_buffer = (get_width_of_terminal() - 12) as usize;
if UnicodeWidthStr::width(&*name) > terminal_plus_buffer {
let trimmed_name = name
.chars()