Compare commits

...

69 Commits

Author SHA1 Message Date
andy.boot
36fd75b9ee Update packages 2021-01-16 15:12:52 +00:00
andy.boot
51a0ccb1ed Increment version 2021-01-16 15:05:33 +00:00
andy.boot
68b0dd5562 [core] New flag: width
Add support for width flag
https://github.com/bootandy/dust/issues/126

Requested because some people may cat the output

All terminal height/width detection is now in the main file. One method
now has too many args for clippy, this complaint is valid and in the
future we should consider pulling these out into a separate object.
2021-01-16 15:01:42 +00:00
andy.boot
360143ee91 Notes on how to publish 2021-01-16 14:56:45 +00:00
Alexandru Macovei
998e7fb2f8 Address clippy lints 2021-01-16 14:56:14 +00:00
Alexandru Macovei
a48c7782ac Print correct message when paths are not found 2021-01-16 14:56:14 +00:00
andy.boot
e0347b0b43 Fix: Stop adding extra output lines for multi args
If we provided n names we would previous output screen_height + n lines.
This is wrong. We should only print screen_height lines
2020-11-25 13:46:48 +00:00
andy.boot
5b6b449cbd Upgrade packages 2020-11-25 13:46:48 +00:00
andy.boot
c8568e5674 Code cleanup from previous commit 2020-10-21 22:16:40 +01:00
Tyler Bailey
9d13994526 Cleanup (#121)
* remove unneeded identity .map()

* remove redunant field name in struct

* impl num_siblings for Node

* replace nested if w/ match in get_tree_chars

* remove unneeded field name in struct

* remove redundant println! & logic in display_node

* make get_children_from_node a Node method

* Revert "remove redundant println! & logic in display_node"

This reverts commit 40777025d5.
2020-10-21 22:01:18 +01:00
Sylvestre Ledru
ca8b1efc18 Be less prescriptive for the ansi_term dep 2020-10-08 23:04:54 +01:00
oToToT
c261492325 encode non-printable characters
use stfu8 to encode utf-8 characters
2020-10-08 22:57:01 +01:00
Thomas Gotwig
8d43185439 Publish on Homebrew on Linux 🍺🐧🎉 2020-10-03 09:42:27 +01:00
andy.boot
1d018bc80b Add test to check behaviour of -d flag
Covers the case fixed by the previous commit
2020-09-03 22:17:39 +01:00
andy.boot
2319c3032f Increment version for new release 2020-09-03 21:58:27 +01:00
Ryan Winograd
53af0d486d Change behavior of depth flag
Change the depth flag so that it only changes the depth of displayed
subdirectories, not the depth of the directory size calculation (i.e.,
changing --depth does not change the displayed directory size, rather it
only changes how many levels of subdirectories are shown).
2020-09-03 21:47:52 +01:00
andy.boot
b643abe05e Refactor tests.
Split tests up into 2 files, 'exact output match' and 'substring ouput
match'
2020-09-03 00:04:27 +01:00
andy.boot
695c9b6747 Add test to check number of files flag 2020-09-03 00:04:27 +01:00
andy.boot
8e8f18b9bc Rename test_dir3 to test_dir_unicode
This better represents what it is trying to test
2020-09-03 00:04:27 +01:00
andy.boot
e767be217a Add test for hidden flag 2020-09-03 00:04:27 +01:00
andy.boot
9187cb8ad2 Fix dumb test bug
dir_substring is in the test_dir2 folder not the test_dir3 folder
2020-09-03 00:04:27 +01:00
andy.boot
873456eb97 Add Hide hidden flag
From feature request to respect the .gitignore file. Decided to bundle in
respect for hidden files into the same feature [otherwise if you obey
the .gitignore file you still endup showing the .git directory]
2020-09-03 00:04:27 +01:00
andy.boot
4e1180e502 hack 2020-08-30 10:39:03 +01:00
andy.boot
4ea8d9339e Support listing directories by number of files
Based of https://github.com/bootandy/dust/pull/104

Idea is to allow users to find the number of files in each directory
instead of size.
2020-08-30 10:39:03 +01:00
andy.boot
82237c6bde Use full width of terminal (extra 2 characters)
Make output 2 characters wider to use the full width of terminal
2020-08-19 00:30:46 +01:00
andy.boot
109df305a3 Upgrade packages 2020-08-18 23:12:28 +01:00
andy.boot
d03a9796f4 Refactor display code
Pull out parts into separate functions
2020-08-18 23:12:28 +01:00
andy.boot
8e591f3dd5 Move test directories into test dir 2020-08-18 23:12:28 +01:00
andy.boot
086f11988c Remove stray printlns 2020-08-18 23:12:28 +01:00
andy.boot
52eca1ff78 Change bar graphs
Change bars so we always show 'correct' length for current row this
means we ignore the parent's level
2020-08-18 23:12:28 +01:00
andy.boot
e80c60b027 Do not print filenames that are too long
https://github.com/bootandy/dust/issues/92

Not 100% sure if this code is clean yet, may well be a better way to do
it

Also:
Added test directory with incredably long name as test case.

Update test_symlinks.py for mac runners. Mac test runners create files
with very long names, hence the tests fail periodically unless they look
for a truncated name with '..' at the end.
2020-08-16 21:19:15 +01:00
andy.boot
e118814684 Tests: Remove any entries in existing tmp dir
Delete the existing dir before copying the new dir over it incase its
contents have changed.

Delete & Copy directories before tests are run.

On test runners the tests run in parallel so we can't write and clean
up at the start and end of each test. Unless each test copys the data to
a unique dir name. [This may be a better thing to do in the long run]
2020-08-16 21:19:15 +01:00
andy.boot
1546cf2eba Update README.md
Fix link to packages.
2020-08-13 09:36:25 +01:00
andy.boot
4f06de8044 Notes on how to publish
Install new release locally before pushing
Noted here because I forget how to do this
2020-08-02 14:26:33 +01:00
andy.boot
2ca6fcc9ce Increment version 2020-08-02 14:26:33 +01:00
pom421
278a31971c add brew command in README (#97)
* add brew command in README

* Add an emoji

* remove unncessary space
2020-08-02 11:47:12 +01:00
andy.boot
479e5899c6 Merge pull request #96 from yuqio/change-ansi-red
Change red ANSI color code for size of biggest file
2020-06-29 13:21:28 +01:00
yuqio
c4c173e40e Change red ANSI color code for size of biggest file
The enum variant `ansi_term::Colour::Red` (which uses the ANSI color code `31`)
is used instead of the code `196` to display the size of the largest file. This
means that the red color of the terminal theme is used which is probably
preferred by most users.
2020-06-29 01:00:11 +02:00
andy.boot
b8410ee4da Merge pull request #95 from j2ghz/patch-1
Add packaging status
2020-06-07 13:09:23 +01:00
Jozef Hollý
0cb0396a84 Add packaging status 2020-06-07 11:07:04 +02:00
andy.boot
b1fe078e06 Merge pull request #93 from bootandy/ab-fixes
Ab various minor fixes
2020-06-02 21:04:20 +01:00
andy.boot
b3d446bfef Display: No padding if no bars drawn
If not drawing the percent bars do not add padding to the filenames
https://github.com/bootandy/dust/issues/89
2020-06-02 13:57:46 +01:00
andy.boot
34be81c216 Windows: If no color flag is already set
... then do not print a warning message
https://github.com/bootandy/dust/issues/87
2020-06-02 13:45:12 +01:00
andy.boot
342164d357 Tests: Add Missing copy command
should have been added with previous commit, would occastionaly have
caused tests to fail if tests run in random order
2020-03-28 18:32:40 +00:00
andy.boot
bfa3594fe8 Merge pull request #86 from bootandy/ab-run-tests-tmp
Move from ignore to jwalk &  refactor tests
2020-03-28 16:27:45 +00:00
andy.boot
1f120de168 Run tests on /tmp directory 2020-03-28 16:07:35 +00:00
andy.boot
c0048b2ae4 Handle running with bad parameter
Earlier refactor caused running with: 'dust -' to crash
2020-03-28 15:59:07 +00:00
andy.boot
402a8f8249 Remove thread parameter
Code to limit number of threads removed. ignore crate seems quite good
at using CPUs correctly
2020-03-28 15:59:07 +00:00
andy.boot
7cc7047b28 Move from assert_cli to assert_cmd
assert_cli is deprecated.
This allows us to use 'or' in the output of our integration tests
2020-03-28 15:06:40 +00:00
andy.boot
1953e107c2 Move everything to ignore instead of jwalk 2020-03-22 23:58:07 +00:00
andy.boot
f096e82754 Merge pull request #81 from rivy/fix.cicd-features
Maint/CI ~ fix features option for GHA `cargo ...` and `cross ...`
2020-03-15 09:50:37 +00:00
Roy Ivy III
c3415df4b1 Maint/CI ~ fix features option for GHA cargo ... and cross ... 2020-03-10 14:10:28 -05:00
andy.boot
9452049aff Increment version 2020-03-01 16:23:26 +00:00
andy.boot
09e3c27e70 Merge pull request #80 from bootandy/colours3
Rethink colors
2020-03-01 16:20:00 +00:00
andy.boot
b0bfe654c4 Rethink colors
Use LS_COLORS to print default color of each file or folder

Keep showing largest sub folder in red.
2020-03-01 16:10:22 +00:00
andy.boot
6bc44de495 Merge pull request #74 from rasmushalland/win-perf
Avoid opening all files for reading on windows
2020-03-01 14:49:47 +00:00
Rasmus Halland
efb455c739 Opening files on windows got a lot cheaper.
We avoid passing FILE_READ_DATA to CreateFile.
2020-03-01 14:41:21 +01:00
andy.boot
2c58041885 Clean up windows performance
Instead of generating random values for the drive and inode counter on
windows we return None instead
2020-03-01 14:41:21 +01:00
Rasmus Halland
c30f31c22c Avoid opening all files for reading on windows
It can be very expensive to do that, especially when it causes windows defender to read the files and scan them.
2020-03-01 14:41:21 +01:00
andy.boot
59f2cdfb84 Merge pull request #79 from NeelChotai/colours
use LS_COLORS for largest subdirectories
2020-03-01 00:59:34 +00:00
Neel Chotai
795d91237d update args descriptions 2020-03-01 00:51:36 +00:00
Neel Chotai
26ae050f16 use LS_COLORS for directories 2020-03-01 00:51:32 +00:00
Neel Chotai
58b395e7ee fix compiler warning 2020-03-01 00:49:10 +00:00
andy.boot
3beb2b5274 Merge pull request #78 from bootandy/ab-fix-build
Maybe fix windows 8 support
2020-02-29 23:06:57 +00:00
andy.boot
19d938b05e Maybe fix windows 8 support
enable_ansi_support must be run for windows 10 but is not required and
indeed fails if run on windows < 10.

Disable the expect so the code might run on windows 8

may fix:
https://github.com/bootandy/dust/issues/70
2020-02-29 11:13:13 +00:00
andy.boot
d4daa82297 Merge pull request #77 from bootandy/ab-fix-build
Ab fix build
2020-02-29 01:35:10 +00:00
andy.boot
21097578d9 Remove feature flag from build
The --features flag was empty which caused the build to fail. As the
features flag was not used it was removed.
2020-02-29 00:42:28 +00:00
andy.boot
069dc184a9 Merge pull request #73 from bootandy/readme
Update readme
2020-02-20 20:39:20 +00:00
andy.boot
02fa657128 Update readme 2020-02-20 20:36:35 +00:00
23 changed files with 1409 additions and 1079 deletions

View File

@@ -32,6 +32,12 @@ jobs:
case ${{ matrix.job.os }} in windows-latest) unset JOB_DO_FORMAT_TESTING ;; esac;
echo set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING:-<empty>/false}
echo ::set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING}
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
@@ -50,7 +56,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}" -- -D warnings
args: ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings
min_version:
name: MinSRV # Minimum supported rust version
@@ -149,6 +155,11 @@ jobs:
echo set-output name=DEPLOY::${DEPLOY:-<empty>/false}
echo ::set-output name=DEPLOY::${DEPLOY}
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
# * CARGO_USE_CROSS (truthy)
CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-<empty>/false}
@@ -193,13 +204,13 @@ jobs:
with:
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
command: build
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}"
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
- name: Test
uses: actions-rs/cargo@v1
with:
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
command: test
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}"
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
- name: Archive executable artifacts
uses: actions/upload-artifact@master
with:

668
Cargo.lock generated
View File

@@ -1,597 +1,529 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi",
]
[[package]]
name = "assert_cli"
version = "0.6.3"
name = "assert_cmd"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dc1679af9a1ab4bea16f228b05d18f8363f8327b1fa8db00d2760cfafc6b61e"
dependencies = [
"colored 1.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"environment 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
"doc-comment",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "0.1.7"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace-sys"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "c2-chacha"
version = "0.2.3"
name = "bstr"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
dependencies = [
"ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr",
]
[[package]]
name = "cc"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.0"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "colored"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.11.0",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "crossbeam-channel"
version = "0.4.0"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
dependencies = [
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-epoch"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2",
"maybe-uninit",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.0"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg",
"cfg-if 0.1.10",
"lazy_static",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "difference"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "du-dust"
version = "0.5.0"
version = "0.5.4"
dependencies = [
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jwalk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.12.1",
"assert_cmd",
"clap",
"crossbeam-channel",
"ignore",
"lscolors",
"num_cpus",
"stfu8",
"tempfile",
"terminal_size",
"thousands",
"unicode-width",
"walkdir",
"winapi-util",
]
[[package]]
name = "either"
version = "1.5.3"
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "environment"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "failure"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure_derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.1.14"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4060f4657be78b8e766215b02b18a2e862d83745545de804638e2b545e81aee6"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "globset"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]]
name = "hermit-abi"
version = "0.1.6"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"libc",
]
[[package]]
name = "itoa"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "jwalk"
version = "0.4.0"
name = "ignore"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c"
dependencies = [
"crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.8.1",
"globset",
"lazy_static",
"log",
"memchr",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.66"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
[[package]]
name = "memoffset"
version = "0.5.3"
name = "log"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
dependencies = [
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10",
]
[[package]]
name = "num_cpus"
version = "1.12.0"
name = "lscolors"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
dependencies = [
"hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.12.1",
]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "ppv-lite86"
version = "0.2.6"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro2"
version = "1.0.8"
name = "predicates"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73dd9b7b200044694dfede9edf907c1ca19630908443e9447e624993700c6932"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"difference",
"predicates-core",
]
[[package]]
name = "quote"
version = "1.0.2"
name = "predicates-core"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb3dbeaaf793584e29c58c7e3a82bbb3c7c06b63cea68d13b0e3cddc124104dc"
[[package]]
name = "predicates-tree"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aee95d988ee893cb35c06b148c80ed2cd52c8eea927f50ba7a0be1a786aeab73"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"predicates-core",
"treeline",
]
[[package]]
name = "rand"
version = "0.7.3"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18519b42a40024d661e1714153e9ad0c3de27cd495760ceb09710920f1098b1e"
dependencies = [
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.2.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5"
dependencies = [
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon-core"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
name = "regex-syntax"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
[[package]]
name = "remove_dir_all"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi",
]
[[package]]
name = "rustc-demangle"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc_version"
version = "0.2.3"
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util",
]
[[package]]
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "scopeguard"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "semver"
version = "0.9.0"
name = "stfu8"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf70433e3300a3c395d06606a700cdf4205f4f14dbae2c6833127c6bb22db77"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_json"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static",
"regex",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "tempfile"
version = "3.1.0"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 1.0.0",
"libc",
"rand",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "terminal_size"
version = "0.1.10"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"libc",
"winapi",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
name = "thousands"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
[[package]]
name = "thread_local"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
dependencies = [
"lazy_static",
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "vec_map"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
version = "0.10.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93c6c3420963c5c64bca373b25e77acb562081b9bb4dd5bb864187742186cea9"
[[package]]
name = "winapi"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.3"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
"checksum assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a29ab7c0ed62970beb0534d637a8688842506d0ff9157de83286dacd065c8149"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e4036b9bf40f3cf16aba72a3d65e8a520fc4bafcdc7079aea8f848c58c5b5536"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum colored 1.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8815e2ab78f3a59928fc32e141fbeece88320a240e43f47b2fd64ea3a88a5b3d"
"checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum environment 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f4b14e20978669064c33b4c1e0fb4083412e40fe56cbea2eae80fd7591503ee"
"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
"checksum jwalk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3dbf0a8f61baee43a2918ff50ac6a2d3b2c105bc08ed53bc298779f1263409"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
"checksum serde_json 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "15913895b61e0be854afd32fd4163fcd2a3df34142cf2cb961b310ce694cbf90"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "e25a60e3024df9029a414be05f46318a77c22538861a22170077d0388c0e926e"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -1,7 +1,7 @@
[package]
name = "du-dust"
description = "A more intuitive version of du"
version = "0.5.0"
version = "0.5.4"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2018"
@@ -21,18 +21,23 @@ name = "dust"
path = "src/main.rs"
[dependencies]
ansi_term = "=0.12"
ansi_term = "0.12"
clap = "=2.33"
jwalk = "0.4.0"
num_cpus = "1.12"
terminal_size = "0.1.10"
unicode-width = "0.1.7"
lscolors = "0.7"
num_cpus = "1"
terminal_size = "0.1"
unicode-width = "0.1"
ignore="0.4"
crossbeam-channel = "0.4"
walkdir="2.3"
thousands = "0.2"
stfu8 = "0.2"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"
[dev-dependencies]
assert_cli = "=0.6"
assert_cmd ="1"
tempfile = "=3"

View File

@@ -3,31 +3,40 @@
# Dust
du + rust = dust. Like du but more intuitive
du + rust = dust. Like du but more intuitive.
# Why
Because I want an easy way to see where my disk is being used.
# Demo
![Example](media/snap.png)
## Install
#### Cargo Install
#### Cargo <a href="https://repology.org/project/du-dust/versions"><img src="https://repology.org/badge/vertical-allrepos/du-dust.svg" alt="Packaging status" align="right"></a>
* cargo install du-dust
* `cargo install du-dust`
#### Download Install
#### 🍺 Homebrew (Mac OS)
* Download linux / mac binary from [Releases](https://github.com/bootandy/dust/releases)
* unzip file: tar -xvf _downloaded_file.tar.gz_
* move file to executable path: sudo mv dust /usr/local/bin/
* `brew install dust`
#### 🍺 Homebrew (Linux)
* `brew tap tgotwig/linux-dust && brew install dust`
#### Download
* Download Linux/Mac binary from [Releases](https://github.com/bootandy/dust/releases)
* unzip file: `tar -xvf _downloaded_file.tar.gz`
* move file to executable path: `sudo mv dust /usr/local/bin/`
## Overview
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 1 'Did not have permissions message'.
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'.
Dust will list the terminal height - 10 biggest sub directories 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 sub directory will have its size shown in *red*
## Why?
du has a number of ways of showing you what it finds, in terms of disk consumption, but really, there are only one or two ways you invoke it: with -h for “human readable” units, like 100G or 89k, or with -b for “bytes”. The former is generally used for a quick survey of a directory with a small number of things in it, and the latter for when you have a bunch and need to sort the output numerically, and youre obligated to either further pass it into something like awk to turn bytes into the appropriate human-friendly unit like mega or gigabytes, or pipe thru sort and head while remembering the '-h' flag. Then once you have the top offenders, you recurse down into the largest one and repeat the process until youve found your cruft or gems and can move on.
Dust assumes thats what you wanted to do in the first place, and takes care of tracking the largest offenders in terms of actual size, and showing them to you with human-friendly units and in-context within the filetree.
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.
## Usage
@@ -37,48 +46,12 @@ 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 not 20)
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 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 the ASCII bars)
```
```
$ dust target
15M ┌── build │ ░█ │ 2%
25M ├── deps │ ░█ │ 4%
45M ┌─┴ release │ ██ │ 7%
84M │ ┌── build │ ▒▒▒▒▒████ │ 13%
7.6M │ │ ┌── libsynstructure-f7552412787ad339.rlib│ ▒▒▒▓▓▓▓▓█ │ 1%
16M │ │ ├── libfailure_derive-e18365d3e6be2e2c.so│ ▒▒▒▓▓▓▓▓█ │ 2%
18M │ │ ├── libsyn-9ad95b745845d5dd.rlib │ ▒▒▒▓▓▓▓▓█ │ 3%
19M │ │ ├── libsyn-d4a3458fcb1c592c.rlib │ ▒▒▒▓▓▓▓▓█ │ 3%
135M │ ├─┴ deps │ ▒▒▒██████ │ 20%
228M │ ┌─┴ debug │ █████████ │ 34%
228M ├─┴ rls │ █████████ │ 34%
18M │ ┌── dust │ ░░░░░░░░░░░░░░█ │ 3%
22M │ ├── dust-a0c31c4633c5fc8b │ ░░░░░░░░░░░░░░█ │ 3%
7.4M │ │ ┌── s-fkrj3vfncf-19aj951-1fv3o6tzvr348 │ ░░░░░░░░░░░░░▒█ │ 1%
7.4M │ │ ┌─┴ dust-1i3xquz5fns51 │ ░░░░░░░░░░░░░▒█ │ 1%
40M │ ├─┴ incremental │ ░░░░░░░░░░░░░██ │ 6%
41M │ ├── build │ ░░░░░░░░░░░░░██ │ 6%
7.6M │ │ ┌── libsynstructure-f7552412787ad339.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
8.2M │ │ ├── libserde-ab4b407a415bc8fc.rmeta │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
9.4M │ │ ├── libserde-ab4b407a415bc8fc.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
11M │ │ ├── tests_symlinks-bf063461b7be6a99 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
11M │ │ ├── integration-08f999d253e3b70c │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
15M │ │ ├── dust-1c6e63725d641738 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
16M │ │ ├── libfailure_derive-e18365d3e6be2e2c.so │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
18M │ │ ├── dust-3a419f62b84d73c1 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
18M │ │ ├── dust-2bdf724d4a721d31 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
18M │ │ ├── libsyn-9ad95b745845d5dd.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
23M │ │ ├── libclap-0dedc35af3ef0670.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
267M │ ├─┴ deps │ ░░░░███████████ │ 40%
392M ├─┴ debug │ ███████████████ │ 59%
667M ┌─┴ target │█████████████████████████ │ 100%
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)
```

View File

@@ -1,6 +1,10 @@
# ----------- To do a release ---------
# tag a commit and push:
# edit version in cargo.toml
# tag a commit and push (increment version first):
# git tag 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]
#cargo install --path .

BIN
media/snap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,57 +1,46 @@
extern crate ansi_term;
use self::ansi_term::Colour::Fixed;
use self::ansi_term::Style;
use crate::utils::Node;
use crate::utils::{Errors, Node};
use terminal_size::{terminal_size, Height, Width};
use self::ansi_term::Colour::Red;
use lscolors::{LsColors, Style};
use unicode_width::UnicodeWidthStr;
use stfu8::encode_u8;
use std::cmp::max;
use std::cmp::min;
use std::fs;
use std::iter::repeat;
use std::path::Path;
use thousands::Separable;
static UNITS: [char; 4] = ['T', 'G', 'M', 'K'];
static BLOCKS: [char; 5] = ['█', '▓', '▒', '░', ' '];
static DEFAULT_TERMINAL_WIDTH: u16 = 80;
pub struct DisplayData {
pub short_paths: bool,
pub is_reversed: bool,
pub colors_on: bool,
pub by_filecount: bool,
pub num_chars_needed_on_left_most: usize,
pub base_size: u64,
pub longest_string_length: usize,
pub ls_colors: LsColors,
}
impl DisplayData {
#[allow(clippy::collapsible_if)]
fn get_tree_chars(&self, was_i_last: bool, has_children: bool) -> &'static str {
if self.is_reversed {
if was_i_last {
if has_children {
"─┴"
} else {
"┌──"
}
} else if has_children {
"├─"
} else {
"├──"
}
} else {
if was_i_last {
if has_children {
"└─┬"
} else {
"└──"
}
} else if has_children {
"├─┬"
} else {
"├──"
}
match (self.is_reversed, was_i_last, has_children) {
(true, true, true) => "┌─┴",
(true, true, false) => "┌──",
(true, false, true) => "─┴",
(true, false, false) => "├──",
(false, true, true) => "└─┬",
(false, true, false) => "└──",
(false, false, true) => "├─┬",
(false, false, false) => "├─",
}
}
@@ -81,15 +70,6 @@ impl DisplayData {
}
}
fn get_children_from_node(node: Node, is_reversed: bool) -> impl Iterator<Item = Node> {
if is_reversed {
let n: Vec<Node> = node.children.into_iter().rev().map(|a| a).collect();
n.into_iter()
} else {
node.children.into_iter()
}
}
struct DrawData<'a> {
indent: String,
percent_bar: String,
@@ -102,13 +82,14 @@ impl DrawData<'_> {
self.indent.to_string() + chars
}
// TODO: can we test this?
fn generate_bar(&self, node: &Node, level: usize) -> String {
let chars_in_bar = self.percent_bar.chars().count();
let num_bars = chars_in_bar as f32 * self.display_data.percent_size(node);
let mut num_not_my_bar = (chars_in_bar as i32) - num_bars as i32;
let mut new_bar = "".to_string();
let idx = 5 - min(5, max(1, level));
let idx = 5 - min(4, max(1, level));
for c in self.percent_bar.chars() {
num_not_my_bar -= 1;
@@ -124,32 +105,37 @@ impl DrawData<'_> {
}
}
fn get_width_of_terminal() -> u16 {
// Windows CI runners detect a very low terminal width
if let Some((Width(w), Height(_h))) = terminal_size() {
max(w, DEFAULT_TERMINAL_WIDTH)
} else {
DEFAULT_TERMINAL_WIDTH
}
}
#[allow(clippy::too_many_arguments)]
pub fn draw_it(
permissions: bool,
errors: Errors,
use_full_path: bool,
is_reversed: bool,
no_colors: bool,
no_percents: bool,
terminal_width: usize,
by_filecount: bool,
root_node: Node,
) {
if !permissions {
if errors.permissions {
eprintln!("Did not have permissions for all directories");
}
if errors.not_found {
eprintln!("Not all directories were found");
}
let num_chars_needed_on_left_most = if by_filecount {
let max_size = root_node.children.iter().map(|n| n.size).fold(0, max);
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;
let num_indent_chars = 3;
let longest_string_length = root_node
.children
.iter()
.map(|c| find_longest_dir_name(&c, " ", !use_full_path))
.map(|c| find_longest_dir_name(&c, num_indent_chars, terminal_width, !use_full_path))
.fold(0, max);
let terminal_width = get_width_of_terminal() - 16;
let max_bar_length = if no_percents || longest_string_length >= terminal_width as usize {
0
@@ -157,48 +143,51 @@ pub fn draw_it(
terminal_width as usize - longest_string_length
};
// handle usize error also add do not show fancy output option
let bar_text = repeat(BLOCKS[0]).take(max_bar_length).collect::<String>();
let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect::<String>();
for c in get_children_from_node(root_node, is_reversed) {
for c in root_node.get_children_from_node(is_reversed) {
let display_data = DisplayData {
short_paths: !use_full_path,
is_reversed,
colors_on: !no_colors,
by_filecount,
num_chars_needed_on_left_most,
base_size: c.size,
longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(),
};
let draw_data = DrawData {
indent: "".to_string(),
percent_bar: bar_text.clone(),
percent_bar: first_size_bar.clone(),
display_data: &display_data,
};
display_node(c, &draw_data, true, true);
}
}
// We can probably pass depth instead of indent here.
// It is ugly to feed in ' ' instead of the actual tree characters but we don't need them yet.
fn find_longest_dir_name(node: &Node, indent: &str, long_paths: bool) -> usize {
let longest = UnicodeWidthStr::width(&*get_printable_name(&node.name, long_paths, indent));
fn find_longest_dir_name(node: &Node, indent: usize, terminal: usize, long_paths: bool) -> usize {
let printable_name = get_printable_name(&node.name, long_paths);
let longest = min(
UnicodeWidthStr::width(&*printable_name) + 1 + indent,
terminal,
);
// each none root tree drawing is 2 chars
let full_indent: String = indent.to_string() + " ";
// each none root tree drawing is 2 more chars, hence we increment indent by 2
node.children
.iter()
.map(|c| find_longest_dir_name(c, &*full_indent, long_paths))
.map(|c| find_longest_dir_name(c, indent + 2, terminal, long_paths))
.fold(longest, max)
}
fn display_node(node: Node, draw_data: &DrawData, is_biggest: bool, is_last: bool) {
let indent2 = draw_data.get_new_indent(!node.children.is_empty(), is_last);
// hacky way of working out how deep we are in the tree
let level = ((indent2.chars().count() - 1) / 2) - 1;
let indent = draw_data.get_new_indent(!node.children.is_empty(), is_last);
let level = ((indent.chars().count() - 1) / 2) - 1;
let bar_text = draw_data.generate_bar(&node, level);
let to_print = format_string(
&node,
&*indent2,
&*indent,
&*bar_text,
is_biggest,
draw_data.display_data,
@@ -209,13 +198,17 @@ fn display_node(node: Node, draw_data: &DrawData, is_biggest: bool, is_last: boo
}
let dd = DrawData {
indent: clean_indentation_string(&*indent2),
indent: clean_indentation_string(&*indent),
percent_bar: bar_text,
display_data: draw_data.display_data,
};
let num_siblings = node.children.len() as u64;
for (count, c) in get_children_from_node(node, draw_data.display_data.is_reversed).enumerate() {
let num_siblings = node.num_siblings();
for (count, c) in node
.get_children_from_node(draw_data.display_data.is_reversed)
.enumerate()
{
let is_biggest = dd.display_data.is_biggest(count, num_siblings);
let was_i_last = dd.display_data.is_last(count, num_siblings);
display_node(c, &dd, is_biggest, was_i_last);
@@ -243,7 +236,7 @@ fn clean_indentation_string(s: &str) -> String {
is
}
fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool, indentation: &str) -> String {
fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool) -> String {
let dir_name = dir_name.as_ref();
let printable_name = {
if long_paths {
@@ -258,7 +251,33 @@ fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool, indentatio
dir_name
}
};
format!("{} {}", indentation, printable_name.display())
encode_u8(printable_name.display().to_string().as_bytes())
}
fn pad_or_trim_filename(node: &Node, indent: &str, display_data: &DisplayData) -> String {
let name = get_printable_name(&node.name, display_data.short_paths);
let indent_and_name = format!("{} {}", indent, name);
let width = UnicodeWidthStr::width(&*indent_and_name);
// 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>());
maybe_trim_filename(name_and_padding, display_data)
}
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>();
name + ".."
} else {
name_in
}
}
pub fn format_string(
@@ -268,35 +287,60 @@ pub fn format_string(
is_biggest: bool,
display_data: &DisplayData,
) -> String {
let pretty_size = format!("{:>5}", human_readable_number(node.size));
let (percents, name_and_padding) = get_name_percent(node, indent, percent_bar, display_data);
let pretty_size = get_pretty_size(node, is_biggest, display_data);
let pretty_name = get_pretty_name(node, name_and_padding, display_data);
format!("{} {} {}{}", pretty_size, indent, pretty_name, percents)
}
let percent_size = display_data.percent_size(node);
let percent_size_str = format!("{:.0}%", percent_size * 100.0);
let tree_and_path = get_printable_name(&node.name, display_data.short_paths, &*indent);
let printable_chars = UnicodeWidthStr::width(&*tree_and_path);
let tree_and_path = tree_and_path
+ &(repeat(" ")
.take(display_data.longest_string_length - printable_chars)
.collect::<String>());
let percents = if percent_bar != "" {
format!("{}{:>4}", percent_bar, percent_size_str)
fn get_name_percent(
node: &Node,
indent: &str,
bar_chart: &str,
display_data: &DisplayData,
) -> (String, String) {
if !bar_chart.is_empty() {
let percent_size_str = format!("{:.0}%", display_data.percent_size(node) * 100.0);
let percents = format!("{}{:>4}", bar_chart, percent_size_str);
let name_and_padding = pad_or_trim_filename(node, indent, display_data);
(percents, name_and_padding)
} else {
"".into()
let n = get_printable_name(&node.name, display_data.short_paths);
let name = maybe_trim_filename(n, display_data);
("".into(), name)
}
}
fn get_pretty_size(node: &Node, is_biggest: bool, display_data: &DisplayData) -> String {
let output = if display_data.by_filecount {
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>()
} else {
format!("{:>5}", human_readable_number(node.size))
};
format!(
"{} {}{}",
if is_biggest && display_data.colors_on {
Fixed(196).paint(pretty_size)
} else {
Style::new().paint(pretty_size)
},
tree_and_path,
percents,
)
if is_biggest && display_data.colors_on {
format!("{}", Red.paint(output))
} else {
output
}
}
fn get_pretty_name(node: &Node, name_and_padding: String, display_data: &DisplayData) -> String {
if display_data.colors_on {
let meta_result = fs::metadata(node.name.clone());
let directory_color = display_data
.ls_colors
.style_for_path_with_metadata(node.name.clone(), meta_result.as_ref().ok());
let ansi_style = directory_color
.map(Style::to_ansi_term_style)
.unwrap_or_default();
format!("{}", ansi_style.paint(name_and_padding))
} else {
name_and_padding
}
}
fn human_readable_number(size: u64) -> String {
@@ -316,6 +360,63 @@ fn human_readable_number(size: u64) -> String {
mod tests {
#[allow(unused_imports)]
use super::*;
#[allow(unused_imports)]
use std::path::PathBuf;
#[cfg(test)]
fn get_fake_display_data(longest_string_length: usize) -> DisplayData {
DisplayData {
short_paths: true,
is_reversed: false,
colors_on: false,
by_filecount: false,
num_chars_needed_on_left_most: 5,
base_size: 1,
longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(),
}
}
#[test]
fn test_format_str() {
let n = Node {
name: PathBuf::from("/short"),
size: 2_u64.pow(12), // This is 4.0K
children: vec![],
};
let indent = "┌─┴";
let percent_bar = "";
let is_biggest = false;
let s = format_string(
&n,
indent,
percent_bar,
is_biggest,
&get_fake_display_data(6),
);
assert_eq!(s, " 4.0K ┌─┴ short");
}
#[test]
fn test_format_str_long_name() {
let name = "very_long_name_longer_than_the_eighty_character_limit_very_long_name_this_bit_will_truncate";
let n = Node {
name: PathBuf::from(name),
size: 2_u64.pow(12), // This is 4.0K
children: vec![],
};
let indent = "┌─┴";
let percent_bar = "";
let is_biggest = false;
let dd = get_fake_display_data(64);
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.."
);
}
#[test]
fn test_human_readable_number() {

View File

@@ -1,6 +1,9 @@
#[macro_use]
extern crate clap;
extern crate crossbeam_channel as channel;
extern crate ignore;
extern crate unicode_width;
extern crate walkdir;
use self::display::draw_it;
use crate::utils::is_a_parent_of;
@@ -8,20 +11,38 @@ use clap::{App, AppSettings, Arg};
use std::cmp::max;
use std::path::PathBuf;
use terminal_size::{terminal_size, Height, Width};
use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, trim_deep_ones, Node};
use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, Node};
mod display;
mod utils;
static DEFAULT_NUMBER_OF_LINES: usize = 30;
static DEFAULT_TERMINAL_WIDTH: usize = 80;
#[cfg(windows)]
fn init_color() {
ansi_term::enable_ansi_support().expect("Couldn't enable color support");
fn init_color(no_color: bool) -> bool {
// If no color is already set do not print a warning message
if no_color {
true
} else {
// Required for windows 10
// Fails to resolve for windows 8 so disable color
match ansi_term::enable_ansi_support() {
Ok(_) => no_color,
Err(_) => {
eprintln!(
"This version of Windows does not support ANSI colors, setting no_color flag"
);
true
}
}
}
}
#[cfg(not(windows))]
fn init_color() {}
fn init_color(no_color: bool) -> bool {
no_color
}
fn get_height_of_terminal() -> usize {
// Windows CI runners detect a terminal height of 0
@@ -32,9 +53,16 @@ fn get_height_of_terminal() -> usize {
}
}
fn main() {
init_color();
fn get_width_of_terminal() -> usize {
// Windows CI runners detect a very low terminal width
if let Some((Width(w), Height(_h))) = terminal_size() {
max(w as usize, DEFAULT_TERMINAL_WIDTH)
} else {
DEFAULT_TERMINAL_WIDTH
}
}
fn main() {
let default_height = get_height_of_terminal();
let def_num_str = default_height.to_string();
@@ -49,18 +77,11 @@ fn main() {
.help("Depth to show")
.takes_value(true),
)
.arg(
Arg::with_name("threads")
.short("t")
.long("threads")
.help("Number of threads to spawn simultaneously")
.takes_value(true),
)
.arg(
Arg::with_name("number_of_lines")
.short("n")
.long("number-of-lines")
.help("Number of lines of output to show")
.help("Number of lines of output to show. This is Height, (but h is help)")
.takes_value(true)
.default_value(def_num_str.as_ref()),
)
@@ -68,7 +89,7 @@ fn main() {
Arg::with_name("display_full_paths")
.short("p")
.long("full-paths")
.help("If set sub directories will not have their path shortened"),
.help("Subdirectories will not have their path shortened"),
)
.arg(
Arg::with_name("ignore_directory")
@@ -77,38 +98,59 @@ fn main() {
.takes_value(true)
.number_of_values(1)
.multiple(true)
.help("Exclude any file or directory with this name."),
.help("Exclude any file or directory with this name"),
)
.arg(
Arg::with_name("limit_filesystem")
.short("x")
.long("limit-filesystem")
.help("Only count the files and directories in the same filesystem as the supplied directory"),
.help("Only count the files and directories on the same filesystem as the supplied directory"),
)
.arg(
Arg::with_name("display_apparent_size")
.short("s")
.long("apparent-size")
.help("If set will use file length. Otherwise we use blocks"),
.help("Use file length instead of blocks"),
)
.arg(
Arg::with_name("reverse")
.short("r")
.long("reverse")
.help("If applied tree will be printed upside down (biggest highest)"),
.help("Print tree upside down (biggest highest)"),
)
.arg(
Arg::with_name("no_colors")
.short("c")
.long("no_colors")
.help("If applied no colors will be printed (normally largest directories are marked in red"),
.long("no-colors")
.help("No colors will be printed (normally largest directories are colored)"),
)
.arg(
Arg::with_name("no_bars")
.short("b")
.long("no_percent_bars")
.help("If applied no percent bars or percents will be displayed"),
.long("no-percent-bars")
.help("No percent bars or percentages will be displayed"),
)
.arg(
Arg::with_name("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'
.long("ignore_hidden")
.help("Obey .git_ignore rules & Do not display hidden files"),
)
.arg(
Arg::with_name("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))
.get_matches();
@@ -127,27 +169,14 @@ fn main() {
}
};
let temp_threads = options.value_of("threads").and_then(|threads| {
threads
.parse::<usize>()
.map_err(|_| eprintln!("Ignoring bad value for threads: {:?}", threads))
.ok()
});
// Bug in JWalk
// https://github.com/jessegrosjean/jwalk/issues/15
// We force it to use 2 threads if there is only 1 cpu
// as JWalk breaks if it tries to run on a single cpu
let threads = {
if temp_threads.is_none() && num_cpus::get() == 1 {
Some(2)
} else {
temp_threads
}
let terminal_width = match value_t!(options.value_of("width"), usize) {
Ok(v) => v,
Err(_) => get_width_of_terminal(),
};
let depth = options.value_of("depth").and_then(|depth| {
depth
.parse::<u64>()
.parse::<usize>()
.map(|v| v + 1)
.map_err(|_| eprintln!("Ignoring bad value for depth"))
.ok()
@@ -157,41 +186,47 @@ fn main() {
return;
}
let no_colors = init_color(options.is_present("no_colors"));
let use_apparent_size = options.is_present("display_apparent_size");
let limit_filesystem = options.is_present("limit_filesystem");
let ignore_directories = match options.values_of("ignore_directory") {
Some(i) => Some(i.map(PathBuf::from).collect()),
None => None,
};
let by_filecount = options.is_present("by_filecount");
let show_hidden = !options.is_present("ignore_hidden");
let simplified_dirs = simplify_dir_names(target_dirs);
let (permissions, nodes) = get_dir_tree(
let (errors, nodes) = get_dir_tree(
&simplified_dirs,
&ignore_directories,
use_apparent_size,
limit_filesystem,
threads,
by_filecount,
show_hidden,
);
let sorted_data = sort(nodes);
let biggest_ones = {
match depth {
None => find_big_ones(sorted_data, number_of_lines + simplified_dirs.len()),
Some(d) => trim_deep_ones(sorted_data, d, &simplified_dirs),
None => find_big_ones(sorted_data, number_of_lines),
Some(_) => sorted_data,
}
};
let tree = build_tree(biggest_ones, depth);
draw_it(
permissions,
errors,
options.is_present("display_full_paths"),
!options.is_present("reverse"),
options.is_present("no_colors"),
no_colors,
options.is_present("no_bars"),
terminal_width,
by_filecount,
tree,
);
}
fn build_tree(biggest_ones: Vec<(PathBuf, u64)>, depth: Option<u64>) -> Node {
fn build_tree(biggest_ones: Vec<(PathBuf, u64)>, depth: Option<usize>) -> Node {
let mut top_parent = Node::default();
// assume sorted order
@@ -206,7 +241,7 @@ fn build_tree(biggest_ones: Vec<(PathBuf, u64)>, depth: Option<u64>) -> Node {
top_parent
}
fn recursively_build_tree(parent_node: &mut Node, new_node: Node, depth: Option<u64>) {
fn recursively_build_tree(parent_node: &mut Node, new_node: Node, depth: Option<usize>) {
let new_depth = match depth {
None => None,
Some(0) => return,

View File

@@ -1,15 +1,22 @@
use jwalk::DirEntry;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use jwalk::WalkDir;
use channel::Receiver;
use std::thread::JoinHandle;
use ignore::{WalkBuilder, WalkState};
use std::sync::atomic;
use std::thread;
mod platform;
use self::platform::*;
#[derive(Debug, Default, Eq)]
type PathData = (PathBuf, u64, Option<(u64, u64)>);
#[derive(Debug, Default, Eq, Clone)]
pub struct Node {
pub name: PathBuf,
pub size: u64,
@@ -38,10 +45,30 @@ impl PartialEq for Node {
}
}
impl Node {
pub fn num_siblings(&self) -> u64 {
self.children.len() as u64
}
pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = Node> {
if is_reversed {
let children: Vec<Node> = self.children.clone().into_iter().rev().collect();
children.into_iter()
} else {
self.children.clone().into_iter()
}
}
}
pub struct Errors {
pub permissions: bool,
pub not_found: bool,
}
pub 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))
child.starts_with(parent) && !parent.starts_with(child)
}
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
@@ -70,43 +97,147 @@ pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf>
top_level_names
}
fn prepare_walk_dir_builder<P: AsRef<Path>>(
top_level_names: &HashSet<P>,
limit_filesystem: bool,
show_hidden: bool,
) -> WalkBuilder {
let mut it = top_level_names.iter();
let mut builder = WalkBuilder::new(it.next().unwrap());
builder.follow_links(false);
if show_hidden {
builder.hidden(false);
builder.ignore(false);
builder.git_global(false);
builder.git_ignore(false);
builder.git_exclude(false);
}
if limit_filesystem {
builder.same_file_system(true);
}
for b in it {
builder.add(b);
}
builder
}
fn is_not_found(e: &ignore::Error) -> bool {
use ignore::Error;
if let Error::WithPath { err, .. } = e {
if let Error::Io(e) = &**err {
if e.kind() == std::io::ErrorKind::NotFound {
return true;
}
}
}
false
}
pub fn get_dir_tree<P: AsRef<Path>>(
top_level_names: &HashSet<P>,
ignore_directories: &Option<Vec<PathBuf>>,
apparent_size: bool,
limit_filesystem: bool,
threads: Option<usize>,
) -> (bool, HashMap<PathBuf, u64>) {
let mut permissions = 0;
let mut data: HashMap<PathBuf, u64> = HashMap::new();
let restricted_filesystems = if limit_filesystem {
get_allowed_filesystems(top_level_names)
} else {
None
};
by_filecount: bool,
show_hidden: bool,
) -> (Errors, HashMap<PathBuf, u64>) {
let (tx, rx) = channel::bounded::<PathData>(1000);
for b in top_level_names.iter() {
examine_dir(
b,
apparent_size,
&restricted_filesystems,
ignore_directories,
&mut data,
&mut permissions,
threads,
);
}
(permissions == 0, data)
let permissions_flag = AtomicBool::new(false);
let not_found_flag = AtomicBool::new(false);
let t2 = top_level_names
.iter()
.map(|p| p.as_ref().to_path_buf())
.collect();
let t = create_reader_thread(rx, t2, apparent_size);
let walk_dir_builder = prepare_walk_dir_builder(top_level_names, limit_filesystem, show_hidden);
walk_dir_builder.build_parallel().run(|| {
let txc = tx.clone();
let pf = &permissions_flag;
let nf = &not_found_flag;
Box::new(move |path| {
match path {
Ok(p) => {
if let Some(dirs) = ignore_directories {
let path = p.path();
let parts = path.components().collect::<Vec<std::path::Component>>();
for d in dirs {
if parts
.windows(d.components().count())
.any(|window| window.iter().collect::<PathBuf>() == *d)
{
return WalkState::Continue;
}
}
}
let maybe_size_and_inode = get_metadata(&p, apparent_size);
match maybe_size_and_inode {
Some(data) => {
let (size, inode_device) =
if by_filecount { (1, data.1) } else { data };
txc.send((p.into_path(), size, inode_device)).unwrap();
}
None => {
pf.store(true, atomic::Ordering::Relaxed);
}
}
}
Err(e) => {
if is_not_found(&e) {
nf.store(true, atomic::Ordering::Relaxed);
} else {
pf.store(true, atomic::Ordering::Relaxed);
}
}
};
WalkState::Continue
})
});
drop(tx);
let data = t.join().unwrap();
let errors = Errors {
permissions: permissions_flag.load(atomic::Ordering::SeqCst),
not_found: not_found_flag.load(atomic::Ordering::SeqCst),
};
(errors, data)
}
fn get_allowed_filesystems<P: AsRef<Path>>(top_level_names: &HashSet<P>) -> Option<HashSet<u64>> {
let mut limit_filesystems: HashSet<u64> = HashSet::new();
for file_name in top_level_names.iter() {
if let Ok(a) = get_filesystem(file_name) {
limit_filesystems.insert(a);
fn create_reader_thread(
rx: Receiver<PathData>,
top_level_names: HashSet<PathBuf>,
apparent_size: bool,
) -> JoinHandle<HashMap<PathBuf, u64>> {
// Receiver thread
thread::spawn(move || {
let mut hash: HashMap<PathBuf, u64> = HashMap::new();
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
for dent in rx {
let (path, size, maybe_inode_device) = dent;
if should_ignore_file(apparent_size, &mut inodes, maybe_inode_device) {
continue;
} else {
for p in path.ancestors() {
let s = hash.entry(p.to_path_buf()).or_insert(0);
*s += size;
if top_level_names.contains(p) {
break;
}
}
}
}
}
Some(limit_filesystems)
hash
})
}
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
@@ -119,96 +250,23 @@ pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
path.as_ref().components().collect::<PathBuf>()
}
fn examine_dir<P: AsRef<Path>>(
top_dir: P,
apparent_size: bool,
filesystems: &Option<HashSet<u64>>,
ignore_directories: &Option<Vec<PathBuf>>,
data: &mut HashMap<PathBuf, u64>,
file_count_no_permission: &mut u64,
threads: Option<usize>,
) {
let top_dir = top_dir.as_ref();
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
let mut iter = WalkDir::new(top_dir)
.preload_metadata(true)
.skip_hidden(false);
if let Some(threads_to_start) = threads {
iter = iter.num_threads(threads_to_start);
}
'entry: for entry in iter {
if let Ok(e) = entry {
let maybe_size_and_inode = get_metadata(&e, apparent_size);
if let Some(dirs) = ignore_directories {
let path = e.path();
let parts = path.components().collect::<Vec<std::path::Component>>();
for d in dirs {
let seq = d.components().collect::<Vec<std::path::Component>>();
if parts
.windows(seq.len())
.any(|window| window.iter().collect::<PathBuf>() == *d)
{
continue 'entry;
}
}
}
match maybe_size_and_inode {
Some((size, inode, device)) => {
if !should_ignore_file(apparent_size, filesystems, &mut inodes, inode, device) {
process_file_with_size_and_inode(top_dir, data, e, size)
}
}
None => *file_count_no_permission += 1,
}
} else {
*file_count_no_permission += 1
}
}
}
fn should_ignore_file(
apparent_size: bool,
restricted_filesystems: &Option<HashSet<u64>>,
inodes: &mut HashSet<(u64, u64)>,
inode: u64,
device: u64,
maybe_inode_device: Option<(u64, u64)>,
) -> bool {
// Ignore files on different devices (if flag applied)
if let Some(rs) = restricted_filesystems {
if !rs.contains(&device) {
return true;
}
}
if !apparent_size {
// Ignore files already visited or symlinked
if inodes.contains(&(inode, device)) {
return true;
}
inodes.insert((inode, device));
}
false
}
fn process_file_with_size_and_inode<P: AsRef<Path>>(
top_dir: P,
data: &mut HashMap<PathBuf, u64>,
e: DirEntry,
size: u64,
) {
let top_dir = top_dir.as_ref();
// This path and all its parent paths have their counter incremented
for path in e.path().ancestors() {
// This is required due to bug in Jwalk that adds '/' to all sub dir lists
// see: https://github.com/jessegrosjean/jwalk/issues/13
if path.to_string_lossy() == "/" && top_dir.to_string_lossy() != "/" {
continue;
}
let s = data.entry(normalize_path(path)).or_insert(0);
*s += size;
if path.starts_with(top_dir) && top_dir.starts_with(path) {
break;
match maybe_inode_device {
None => false,
Some(data) => {
let (inode, device) = data;
if !apparent_size {
// Ignore files already visited or symlinked
if inodes.contains(&(inode, device)) {
return true;
}
inodes.insert((inode, device));
}
false
}
}
}
@@ -236,36 +294,6 @@ pub fn find_big_ones(new_l: Vec<(PathBuf, u64)>, max_to_show: usize) -> Vec<(Pat
}
}
fn depth_of_path(name: &PathBuf) -> usize {
// Filter required as paths can have some odd preliminary
// ("Prefix") bits (for example, from windows, "\\?\" or "\\UNC\")
name.components()
.filter(|&c| match c {
std::path::Component::Prefix(_) => false,
_ => true,
})
.count()
}
pub fn trim_deep_ones(
input: Vec<(PathBuf, u64)>,
max_depth: u64,
top_level_names: &HashSet<PathBuf>,
) -> Vec<(PathBuf, u64)> {
let mut result: Vec<(PathBuf, u64)> = Vec::with_capacity(input.len() * top_level_names.len());
for name in top_level_names {
let my_max_depth = depth_of_path(name) + max_depth as usize;
for &(ref k, ref v) in input.iter() {
if k.starts_with(name) && depth_of_path(k) <= my_max_depth {
result.push((k.clone(), *v));
}
}
}
result
}
mod tests {
#[allow(unused_imports)]
use super::*;
@@ -352,14 +380,14 @@ mod tests {
let mut files = HashSet::new();
files.insert((10, 20));
assert!(!should_ignore_file(true, &None, &mut files, 0, 0));
assert!(!should_ignore_file(true, &mut files, Some((0, 0))));
// New file is not known it will be inserted to the hashmp and should not be ignored
assert!(!should_ignore_file(false, &None, &mut files, 11, 12));
assert!(!should_ignore_file(false, &mut files, Some((11, 12))));
assert!(files.contains(&(11, 12)));
// The same file will be ignored the second time
assert!(should_ignore_file(false, &None, &mut files, 11, 12));
assert!(should_ignore_file(false, &mut files, Some((11, 12))));
}
#[test]
@@ -367,17 +395,8 @@ mod tests {
let mut files = HashSet::new();
files.insert((10, 20));
let mut devices = HashSet::new();
devices.insert(99);
let od = Some(devices);
// If we are looking at a different device (disk) and the device flag is set
// then apparent_size is irrelevant - we ignore files on other devices
assert!(should_ignore_file(false, &od, &mut files, 11, 12));
assert!(should_ignore_file(true, &od, &mut files, 11, 12));
// We do not ignore files on the same device
assert!(!should_ignore_file(false, &od, &mut files, 2, 99));
assert!(!should_ignore_file(true, &od, &mut files, 2, 99));
assert!(!should_ignore_file(false, &mut files, Some((2, 99))));
assert!(!should_ignore_file(true, &mut files, Some((2, 99))));
}
}

View File

@@ -1,8 +1,6 @@
use jwalk::DirEntry;
use ignore::DirEntry;
#[allow(unused_imports)]
use std::fs;
use std::io;
use std::path::Path;
#[cfg(target_family = "unix")]
fn get_block_size() -> u64 {
@@ -12,45 +10,119 @@ fn get_block_size() -> u64 {
}
#[cfg(target_family = "unix")]
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, u64, u64)> {
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::unix::fs::MetadataExt;
d.metadata.as_ref().unwrap().as_ref().ok().map(|md| {
if use_apparent_size {
(md.len(), md.ino(), md.dev())
} else {
(md.blocks() * get_block_size(), md.ino(), md.dev())
match d.metadata() {
Ok(md) => {
if use_apparent_size {
Some((md.len(), Some((md.ino(), md.dev()))))
} else {
Some((md.blocks() * get_block_size(), Some((md.ino(), md.dev()))))
}
}
})
Err(_e) => None,
}
}
#[cfg(target_family = "windows")]
pub fn get_metadata(d: &DirEntry, _use_apparent_size: bool) -> Option<(u64, u64, u64)> {
use winapi_util::file::information;
pub fn get_metadata(d: &DirEntry, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
// On windows opening the file to get size, file ID and volume can be very
// expensive because 1) it causes a few system calls, and more importantly 2) it can cause
// windows defender to scan the file.
// Therefore we try to avoid doing that for common cases, mainly those of
// plain files:
// The idea is to make do with the file size that we get from the OS for
// free as part of iterating a folder. Therefore we want to make sure that
// it makes sense to use that free size information:
// Volume boundaries:
// The user can ask us not to cross volume boundaries. If the DirEntry is a
// plain file and not a reparse point or other non-trivial stuff, we assume
// that the file is located on the same volume as the directory that
// contains it.
// File ID:
// This optimization does deprive us of access to a file ID. As a
// workaround, we just make one up that hopefully does not collide with real
// file IDs.
// Hard links: Unresolved. We don't get inode/file index, so hard links
// count once for each link. Hopefully they are not too commonly in use on
// windows.
// Size:
// We assume (naively?) that for the common cases the free size info is the
// same as one would get by doing the expensive thing. Sparse, encrypted and
// compressed files are not included in the common cases, as one can image
// there being more than view on their size.
// Savings in orders of magnitude in terms of time, io and cpu have been
// observed on hdd, windows 10, some 100Ks files taking up some hundreds of
// GBs:
// Consistently opening the file: 30 minutes.
// With this optimization: 8 sec.
use std::io;
use std::path::Path;
use winapi_util::Handle;
fn handle_from_path_limited<P: AsRef<Path>>(path: P) -> io::Result<Handle> {
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
const FILE_READ_ATTRIBUTES: u32 = 0x0080;
let h = Handle::from_path_any(d.path()).ok()?;
let info = information(&h).ok()?;
// So, it seems that it does does have to be that expensive to open
// files to get their info: Avoiding opening the file with the full
// GENERIC_READ is key:
Some((
info.file_size(),
info.file_index(),
info.volume_serial_number(),
))
}
#[cfg(target_family = "unix")]
pub fn get_filesystem<P: AsRef<Path>>(file_path: P) -> Result<u64, io::Error> {
use std::os::unix::fs::MetadataExt;
let metadata = fs::metadata(file_path)?;
Ok(metadata.dev())
}
#[cfg(target_family = "windows")]
pub fn get_filesystem<P: AsRef<Path>>(file_path: P) -> Result<u64, io::Error> {
use winapi_util::file::information;
use winapi_util::Handle;
let h = Handle::from_path_any(file_path)?;
let info = information(&h)?;
Ok(info.volume_serial_number())
// https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights:
// "For example, a Windows file object maps the GENERIC_READ bit to the
// READ_CONTROL and SYNCHRONIZE standard access rights and to the
// FILE_READ_DATA, FILE_READ_EA, and FILE_READ_ATTRIBUTES
// object-specific access rights"
// The flag FILE_READ_DATA seems to be the expensive one, so we'll avoid
// that, and a most of the other ones. Simply because it seems that we
// don't need them.
let file = OpenOptions::new()
.access_mode(FILE_READ_ATTRIBUTES)
.open(path)?;
Ok(Handle::from_file(file))
}
fn get_metadata_expensive(d: &DirEntry) -> Option<(u64, Option<(u64, u64)>)> {
use winapi_util::file::information;
let h = handle_from_path_limited(d.path()).ok()?;
let info = information(&h).ok()?;
Some((
info.file_size(),
Some((info.file_index(), info.volume_serial_number())),
))
}
use std::os::windows::fs::MetadataExt;
match d.metadata() {
Ok(ref md) => {
const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20u32;
const FILE_ATTRIBUTE_READONLY: u32 = 0x1u32;
const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2u32;
const FILE_ATTRIBUTE_SYSTEM: u32 = 0x4u32;
const FILE_ATTRIBUTE_NORMAL: u32 = 0x80u32;
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10u32;
let attr_filtered = md.file_attributes()
& !(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
if attr_filtered == FILE_ATTRIBUTE_ARCHIVE
|| attr_filtered == FILE_ATTRIBUTE_DIRECTORY
|| md.file_attributes() == FILE_ATTRIBUTE_NORMAL
{
Some((md.len(), None))
} else {
get_metadata_expensive(&d)
}
}
_ => get_metadata_expensive(&d),
}
}

View File

@@ -0,0 +1 @@
hi

View File

272
tests/test_exact_output.rs Normal file
View File

@@ -0,0 +1,272 @@
use assert_cmd::Command;
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
* Windows is ignored here because the results vary by host making exact testing impractical
*
* Despite the above problems, these tests are good as they are the closest to 'the real thing'.
*/
// Warning: File sizes differ on both platform and on the format of the disk.
/// Copy to /tmp dir - we assume that the formatting of the /tmp partition
/// is consistent. If the tests fail your /tmp filesystem probably differs
fn copy_test_data(dir: &str) {
// First remove the existing directory - just incase it is there and has incorrect data
let last_slash = dir.rfind('/').unwrap();
let last_part_of_dir = dir.chars().skip(last_slash).collect::<String>();
match Command::new("rm")
.arg("-rf")
.arg("/tmp/".to_owned() + &*last_part_of_dir)
.ok()
{
Ok(_) => {}
Err(_) => {}
};
match Command::new("cp").arg("-r").arg(dir).arg("/tmp/").ok() {
Ok(_) => {}
Err(err) => {
eprintln!("Error copying directory {:?}", err);
}
};
}
pub fn initialize() {
INIT.call_once(|| {
copy_test_data("tests/test_dir");
copy_test_data("tests/test_dir2");
copy_test_data("tests/test_dir_unicode");
});
}
// "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()));
}
#[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()));
}
#[cfg(target_os = "macos")]
fn main_output() -> String {
r#"
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ many │████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "linux")]
fn main_output() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33%
8.0K ┌─┴ many │ █████████████████████████████████ │ 67%
12K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "windows")]
fn main_output() -> String {
"windows results vary by host".to_string()
}
#[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();
println!("{:?}", output.trim());
println!("{:?}", main_output_long_paths().trim());
assert!(output.contains(&main_output_long_paths()));
}
#[cfg(target_os = "macos")]
fn main_output_long_paths() -> String {
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#"
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()
}
// 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()));
}
#[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..
4.0K ├── dir_name_clash
4.0K │ ┌── hello
8.0K ├─┴ dir_substring
4.0K │ ┌── hello
8.0K ├─┴ dir
24K ┌─┴ test_dir2
"
.trim()
.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..
4.0K │ ┌── hello
4.0K ├─┴ dir_substring
4.0K ├── dir_name_clash
4.0K │ ┌── hello
4.0K ├─┴ dir
12K ┌─┴ test_dir2
"
.trim()
.into()
}
#[cfg(target_os = "windows")]
fn no_substring_of_names_output() -> String {
"PRs".into()
}
#[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()));
}
#[cfg(target_os = "linux")]
fn unicode_dir() -> String {
// The way unicode & asian characters are rendered on the terminal should make this line up
"
0B ┌── 👩.unicode │ █ │ 0%
0B ├── ラウトは難しいです!.japan│ █ │ 0%
4.0K ┌─┴ test_dir_unicode │██████████████████████████████████ │ 100%
"
.trim()
.into()
}
#[cfg(target_os = "macos")]
fn unicode_dir() -> String {
"
0B ┌── 👩.unicode │ █ │ 0%
0B ├── ラウトは難しいです!.japan│ █ │ 0%
0B ┌─┴ test_dir_unicode │ █ │ 0%
"
.trim()
.into()
}
#[cfg(target_os = "windows")]
fn unicode_dir() -> String {
"".into()
}

149
tests/test_flags.rs Normal file
View File

@@ -0,0 +1,149 @@
use assert_cmd::Command;
use std::str;
/**
* This file contains tests that test a substring of the output using '.contains'
*
* These tests should be the same cross platform
*/
// 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();
assert!(output.contains(" ┌─┴ "));
assert!(output.contains("test_dir "));
assert!(output.contains(" ┌─┴ "));
assert!(output.contains("many "));
assert!(output.contains(" ├── "));
assert!(output.contains("hello_file"));
assert!(output.contains(" ┌── "));
assert!(output.contains("a_file "));
}
#[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();
// If bars are not being shown we don't need to pad the output with spaces
assert!(output.contains("many"));
assert!(!output.contains("many "));
}
#[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();
assert!(output.contains(" └─┬ test_dir "));
assert!(output.contains(" └─┬ many "));
assert!(output.contains(" ├── hello_file"));
assert!(output.contains(" └── a_file "));
}
#[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();
assert!(!output.contains("hello_file"));
}
#[test]
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"));
}
// 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();
assert!(!output.contains("dir_substring"));
}
#[test]
pub fn test_with_bad_param() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let stderr = cmd.arg("-").unwrap().stderr;
let stderr = str::from_utf8(&stderr).unwrap();
assert!(stderr.contains("Did not have permissions for all directories"));
}
#[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();
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();
assert!(!output.contains(".hidden_file"));
assert!(output.contains("┌── test_dir_hidden_entries"));
}
#[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();
assert!(output.contains("1 ┌── hello_file"));
assert!(output.contains("1 ├── a_file "));
assert!(output.contains("3 ┌─┴ many"));
assert!(output.contains("4 ┌─┴ test_dir"));
}

View File

@@ -1,259 +1 @@
mod tests_symlinks;
// File sizes differ on both platform and on the format of the disk.
// We can at least test the file names are there
#[test]
pub fn test_basic_output() {
assert_cli::Assert::main_binary()
.with_args(&["src/test_dir/"])
.stdout()
.contains(" ┌─┴ test_dir ")
.stdout()
.contains(" ┌─┴ many ")
.stdout()
.contains(" ├── hello_file")
.stdout()
.contains(" ┌── a_file ")
.unwrap();
}
// fix! [rivy; 2020-22-01] "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
assert_cli::Assert::main_binary()
.with_args(&["-c", "src/test_dir"])
.stdout()
.is(main_output().as_str())
.unwrap();
}
// fix! [rivy; 2020-22-01] "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_multi_arg() {
assert_cli::Assert::main_binary()
.with_args(&["-c", "src/test_dir/many/", "src/test_dir/", "src/test_dir"])
.stdout()
.is(main_output().as_str())
.unwrap();
}
#[cfg(target_os = "macos")]
fn main_output() -> String {
r#"
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│██████████████████████████████████████████████ │ 100%
4.0K ┌─┴ many │██████████████████████████████████████████████ │ 100%
4.0K ┌─┴ test_dir │██████████████████████████████████████████████ │ 100%
"#
.to_string()
}
#[cfg(target_os = "linux")]
fn main_output() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░████████████████ │ 33%
8.0K ┌─┴ many │ ███████████████████████████████ │ 67%
12K ┌─┴ test_dir │██████████████████████████████████████████████ │ 100%
"#
.to_string()
}
#[cfg(target_os = "windows")]
fn main_output() -> String {
"PRs welcome".to_string()
}
// fix! [rivy; 2020-22-01] "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_long_paths() {
assert_cli::Assert::main_binary()
.with_args(&["-c", "-p", "src/test_dir"])
.stdout()
.is(main_output_long_paths().as_str())
.unwrap();
}
#[cfg(target_os = "macos")]
fn main_output_long_paths() -> String {
r#"
0B ┌── src/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── src/test_dir/many/hello_file│████████████████████████████ │ 100%
4.0K ┌─┴ src/test_dir/many │████████████████████████████ │ 100%
4.0K ┌─┴ src/test_dir │████████████████████████████ │ 100%
"#
.to_string()
}
#[cfg(target_os = "linux")]
fn main_output_long_paths() -> String {
r#"
0B ┌── src/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── src/test_dir/many/hello_file│ ░░░░░░░░░██████████ │ 33%
8.0K ┌─┴ src/test_dir/many │ ███████████████████ │ 67%
12K ┌─┴ src/test_dir │████████████████████████████ │ 100%
"#
.to_string()
}
#[cfg(target_os = "windows")]
fn main_output_long_paths() -> String {
"PRs welcome".to_string()
}
// fix! [rivy; 2020-22-01] "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_apparent_size() {
assert_cli::Assert::main_binary()
.with_args(&["-c", "-s", "src/test_dir"])
.stdout()
.is(output_apparent_size().as_str())
.unwrap();
}
#[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%
"#
.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%
"#
.to_string()
}
#[cfg(target_os = "windows")]
fn output_apparent_size() -> String {
"".to_string()
}
#[test]
pub fn test_reverse_flag() {
assert_cli::Assert::main_binary()
.with_args(&["-c", "-r", "src/test_dir/"])
.stdout()
.contains(" └─┬ test_dir ")
.stdout()
.contains(" └─┬ many ")
.stdout()
.contains(" ├── hello_file")
.stdout()
.contains(" └── a_file ")
.unwrap();
}
#[test]
pub fn test_d_flag_works() {
// We should see the top level directory but not the sub dirs / files:
assert_cli::Assert::main_binary()
.with_args(&["-d", "1", "-s", "src/test_dir"])
.stdout()
.doesnt_contain("hello_file")
.unwrap();
}
// Check against directories and files whos names are substrings of each other
// fix! [rivy; 2020-22-01] "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_substring_of_names() {
assert_cli::Assert::main_binary()
.with_args(&["-c", "src/test_dir2"])
.stdout()
.is(no_substring_of_names_output().as_str())
.unwrap();
}
#[cfg(target_os = "linux")]
fn no_substring_of_names_output() -> String {
"
4.0K ┌── dir_name_clash│ ████████ │ 17%
4.0K │ ┌── hello │ ░░░░░░░████████ │ 17%
8.0K ├─┴ dir_substring │ ███████████████ │ 33%
4.0K │ ┌── hello │ ░░░░░░░████████ │ 17%
8.0K ├─┴ dir │ ███████████████ │ 33%
24K ┌─┴ test_dir2 │████████████████████████████████████████████ │ 100%
"
.into()
}
#[cfg(target_os = "macos")]
fn no_substring_of_names_output() -> String {
"
4.0K ┌── hello │ ███████████████ │ 33%
4.0K ┌─┴ dir_substring │ ███████████████ │ 33%
4.0K ├── dir_name_clash│ ███████████████ │ 33%
4.0K │ ┌── hello │ ███████████████ │ 33%
4.0K ├─┴ dir │ ███████████████ │ 33%
12K ┌─┴ test_dir2 │████████████████████████████████████████████ │ 100%
"
.into()
}
#[cfg(target_os = "windows")]
fn no_substring_of_names_output() -> String {
"PRs".into()
}
// fix! [rivy; 2020-22-01] "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_unicode_directories() {
assert_cli::Assert::main_binary()
.with_args(&["-c", "src/test_dir3"])
.stdout()
.is(unicode_dir().as_str())
.unwrap();
}
#[cfg(target_os = "linux")]
fn unicode_dir() -> String {
// The way unicode & asian characters are rendered on the terminal should make this line up
"
0B ┌── 👩.unicode │ █ │ 0%
0B ├── ラウトは難しいです!.japan│ █ │ 0%
4.0K ┌─┴ test_dir3 │████████████████████████████████ │ 100%
"
.into()
}
#[cfg(target_os = "macos")]
fn unicode_dir() -> String {
"
0B ┌── 👩.unicode │ █ │ 0%
0B ├── ラウトは難しいです!.japan│ █ │ 0%
0B ┌─┴ test_dir3 │ █ │ 0%
"
.into()
}
#[cfg(target_os = "windows")]
fn unicode_dir() -> String {
"".into()
}
// Check against directories and files whos names are substrings of each other
#[test]
pub fn test_ignore_dir() {
assert_cli::Assert::main_binary()
.with_args(&["-c", "-X", "dir_substring", "src/test_dir2"])
.stdout()
.doesnt_contain("dir_substring")
.unwrap();
}

View File

@@ -1,14 +1,42 @@
use assert_cmd::Command;
use std::cmp::max;
use std::fs::File;
use std::io::Write;
use std::panic;
use std::path::PathBuf;
use std::process::Command;
use std::str;
use terminal_size::{terminal_size, Height, Width};
use unicode_width::UnicodeWidthStr;
use tempfile::Builder;
use tempfile::TempDir;
// File sizes differ on both platform and on the format of the disk.
// Windows: `ln` is not usually an available command; creation of symbolic links requires special enhanced permissions
fn get_width_of_terminal() -> u16 {
if let Some((Width(w), Height(_h))) = terminal_size() {
max(w, 80)
} else {
80
}
}
// 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;
if UnicodeWidthStr::width(&*name) > terminal_plus_buffer {
let trimmed_name = name
.chars()
.take(terminal_plus_buffer - 2)
.collect::<String>();
trimmed_name + ".."
} else {
name
}
}
fn build_temp_file(dir: &TempDir) -> PathBuf {
let file_path = dir.path().join("notes.txt");
let mut file = File::create(&file_path).unwrap();
@@ -33,19 +61,18 @@ pub fn test_soft_sym_link() {
.output();
assert!(c.is_ok());
let c = format!(" ┌── {}", link_name_s);
let b = format!(" ├── {}", file_path_s);
let c = format!(" ┌── {}", get_file_name(link_name_s.into()));
let b = format!(" ├── {}", get_file_name(file_path_s.into()));
let a = format!("─┴ {}", dir_s);
assert_cli::Assert::main_binary()
.with_args(&["-p", &dir_s])
.stdout()
.contains(a.as_str())
.stdout()
.contains(b.as_str())
.stdout()
.contains(c.as_str())
.unwrap();
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-p").arg("-c").arg(dir_s).unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(a.as_str()));
assert!(output.contains(b.as_str()));
assert!(output.contains(c.as_str()));
}
#[cfg_attr(target_os = "windows", ignore)]
@@ -64,30 +91,18 @@ pub fn test_hard_sym_link() {
.output();
assert!(c.is_ok());
let a = format!(" {}", dir_s);
let b = format!(" ┌── {}", link_name_s);
let b2 = format!(" ┌─{}", file_path_s);
let link_output = format!(" ┌─{}", get_file_name(link_name_s.into()));
let file_output = format!(" ┌── {}", get_file_name(file_path_s.into()));
let dirs_output = format!(" {}", dir_s);
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-p").arg("-c").arg(dir_s).unwrap().stdout;
// Because this is a hard link the file and hard link look identical. Therefore
// we cannot guarantee which version will appear first.
let result = panic::catch_unwind(|| {
assert_cli::Assert::main_binary()
.with_args(&["-p", dir_s])
.stdout()
.contains(a.as_str())
.stdout()
.contains(b.as_str())
.unwrap();
});
if result.is_err() {
assert_cli::Assert::main_binary()
.with_args(&["-p", dir_s])
.stdout()
.contains(a.as_str())
.stdout()
.contains(b2.as_str())
.unwrap();
}
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(dirs_output.as_str()));
assert!(output.contains(link_output.as_str()) || output.contains(file_output.as_str()));
}
#[cfg_attr(target_os = "windows", ignore)]
@@ -107,13 +122,12 @@ pub fn test_recursive_sym_link() {
assert!(c.is_ok());
let a = format!("─┬ {}", dir_s);
let b = format!(" └── {}", link_name_s);
let b = format!(" └── {}", get_file_name(link_name_s.into()));
assert_cli::Assert::main_binary()
.with_args(&["-r", "-p", dir_s])
.stdout()
.contains(a.as_str())
.stdout()
.contains(b.as_str())
.unwrap();
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-p").arg("-c").arg("-r").arg(dir_s).unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(a.as_str()));
assert!(output.contains(b.as_str()));
}