Compare commits

...

379 Commits

Author SHA1 Message Date
andy.boot
137bde1099 Fix: Try to stop panics on android.
Catch panics on thread initialization.
2022-08-26 17:12:39 +01:00
andy.boot
0d0206a5a5 Version: New version 2022-08-25 10:18:18 +01:00
andy.boot
dfce0eec66 Fix: Warn if configuring threads fails
User reported this unwrap could fail, so log an error instead of using a
naked unwrap.
2022-08-25 08:59:15 +01:00
Kian-Meng Ang
c148cd9044 Prettify md/yaml file
Resolved via `prettier --write .`
2022-08-24 08:59:30 +01:00
Kian-Meng Ang
2893f73f47 Fix typos
Found via `codespell -L crate`.
2022-08-24 08:55:00 +01:00
andy.boot
5103ebe0d8 Version: Increment version 2022-08-23 12:26:08 +01:00
andy.boot
40acc8f868 README: Update readme to include max-size 2022-08-23 11:47:18 +01:00
andy.boot
eebd9daf2a Feature: Add error message on bad min-size
Log to stderr if the min-size from either parameter or config is invalid
2022-08-23 11:47:18 +01:00
andy.boot
9bc1a6d625 Refactor: Tidy up use of UNITS: k,m,g,t 2022-08-23 11:47:18 +01:00
andy.boot
d6f9bb3c47 Feature: Add min-size parameter
Add often requested feature. '--min-size 50000' will only include files
above a size of 50kB
2022-08-23 11:47:18 +01:00
andy.boot
f70f4b7e12 CI: Only run against latest ubuntu builds
Copying lsd's CI which only builds against latest ubuntu releases.
2022-08-23 09:30:01 +01:00
andy.boot
b9b2aee760 Cleanup: Clean previous commit.
Dislike the idea of passing a string into build_cli. By removing
a call to default_value we can side-step the problem.

Downside is we lose the error log if a user provides a bad depth
2022-08-23 09:12:07 +01:00
Jedsek
f60184ecbb Fix the hard code of max depth 2022-08-23 09:11:13 +01:00
Jedsek
81d52e6e3a Support for completions 2022-08-23 09:11:13 +01:00
andy.boot
5980858b39 Feature: Config file
Read flags for several params from config file.

This was a popular requested feature (many people want to run with -r on
by default).
2022-08-22 12:05:04 +01:00
andy.boot
ed6a8d0462 Revert tests
Revert symlink tests to their old form - No mix of arg and args & a
comment explaining why we need a large width.
2022-08-20 11:09:56 +01:00
andy.boot
4cef6aaa84 Refactor: init_color function
Refactor function so windows & linux are forced to have same signature
instead of 2 different functions
2022-08-20 11:09:56 +01:00
andy.boot
d477145694 Tests refactor: Neaten test 2022-08-20 11:09:56 +01:00
andy.boot
dc5b7b2c2e Comment: Update comment text 2022-08-20 11:09:56 +01:00
andy.boot
cf5ebd76fe Refactor: Reintroduce local variable 2022-08-20 11:09:56 +01:00
andy.boot
fc548919c5 Refactor: Move filter by file type to new file 2022-08-20 11:09:56 +01:00
andy.boot
4b4bca52d9 Rename variable
Better name, this represents the percent bars just 'percent' is vague
2022-08-20 11:09:56 +01:00
andy.boot
2a9d545c3c Style: Remove ColorState enum, revert init_color
Booleans are simpler and easier to work with.

Revert init_color to its earlier state as it is easier to understand
2022-08-20 11:09:56 +01:00
andy.boot
20cc5cf7e0 Clippy: Fix clippy 2022-08-20 11:09:56 +01:00
Nathan West
5fcc45efbe Use correct error code type 2022-08-20 10:50:46 +01:00
Nathan West
282f6d314d get_new_root no longer panics if there are 0 nodes 2022-08-20 10:50:46 +01:00
Nathan West
c36ca33fe9 Lots of code cleanup
- Try to use iterator adapters and collect in various places, where possible. This especially benefits draw_it.
- Try to use `.map` and other similar methods on Options and Results, where possible
- Replaced nearly all clones with reference-based equivalents
- Summarizing nodes by file extension is now much more efficient
- PartialOrd and PartialEq implementations now agree
- Replace #[cfg(...)] function definitions with simpler if cfg!(...) equivelents
- Simplify CLI Values handling by taking advantage of Values::default
- Various spelling corrections in comments
- Add `ColorState` enum to replace bool, for clarity
- Fix tests that break under some detected terminal widths when paths are long
- Use sort_by instead of (sort, reverse)
- Use new `ExtensionNode` struct internally to simplify extension aggregation code
2022-08-20 10:50:46 +01:00
andy.boot
34ba99af2a Tests: Check stderr is empty in test_flags
For each test_flag test using the build_command helper method, check
that stderr is empty
2022-08-19 10:43:24 +01:00
andy.boot
2713445ad0 Fix: Permissions
Stop incorrect reporting of "Did not have permissions" this would
happen where dust is called with a file instead of a directory.

https://github.com/bootandy/dust/issues/241

todo: add test
2022-08-19 10:43:24 +01:00
andy.boot
b62f35291d Fix: Remove first space char
Before we assumed 5 chars for printing the size of a node. This change
allows us to check if we need 5 chars or if we can manage with 4 which
may save us a character.

(1023B = 5 chars, 1.2K = 4 chars)

Note: Mac test runners create very long filenames in tmp directories. To
fix this we must either
1) Tell the terminal it has a very wide width
2) Duplicate the code to reduce the size of the filename (add .. at the
   end).

This commit changes from (2) to (1)
2022-08-19 10:41:59 +01:00
andy.boot
a7fbcb8156 Refactor: Simplify get_pretty_size
Remove the constant '5' used in format. Now all paths use
num_chars_needed_on_left_most instead.
2022-08-19 10:41:59 +01:00
andy.boot
a7120b949c Tests: Add test to verify skip-total flag 2022-08-18 15:22:10 +01:00
andy.boot
812e1e3c53 Feature: Add skip-total flag
Flag to not include the last line containing totals of the output tree
2022-08-18 15:22:10 +01:00
andy.boot
4eb3f29565 Tests: Predictable iteration order allows stricter
Now files are iterated thru in a preditermined order we can tighten the
boundaries of the test_hard_sym_link test.

Link will not be displayed, only the file name
2022-08-18 13:39:01 +01:00
andy.boot
d64092d8a1 Fix: Predictable iteration order
Sort the Node objects so that duplicate inodes appear in a predictable
order.

Include new sort_by_inode method because this is notably quicker than
sorting by size & name as defined in the nodes.rs file.
2022-08-18 13:16:09 +01:00
andy.boot
77750c8149 Update README.md 2022-08-18 13:15:05 +01:00
andy.boot
b9386cd39e Fix: Increase Stack size
Low stack size could result in a stack overflow exception if traversing
very highly nested directories.

This makes core-dumps less likely but will not completely remove the
risk

example for creating directories:
mkdir -p $(for i in {1..5000}; do echo -n "qwe/"; done)

original issue:
https://github.com/bootandy/dust/issues/197
2022-08-15 14:05:24 +01:00
andy.boot
17112b09cc Clippy: Remove un-needed return 2022-08-15 12:26:47 +01:00
andy.boot
c5adff5348 Clippy: Remove un-needed return 2022-08-15 12:26:47 +01:00
somehowchris
ad2e52e211 perf: use lto, strip and codegen profile settings for perf opt 2022-08-15 11:56:46 +01:00
andy.boot
164bec71a3 Update libs 2022-07-14 09:42:45 +01:00
andy.boot
11b5c7227f New Version 2022-07-14 08:53:45 +01:00
andy.boot
fc70f9ba30 Rename variable 2022-07-13 09:38:21 +01:00
andy.boot
a00d1f0719 Fix: Allow -n to be used with -d
Allow -n for number_of_lines to be used with -d 'max depth'

Remove depth specific functions, the job is now handled by the mainline

Add depth as a field onto the Node object.
2022-07-13 09:38:21 +01:00
andy.boot
c4ea7815f8 Refactor: tweak utils function
Refactor simplify_dir_names to make it more readable
2022-07-01 12:04:31 +01:00
dependabot[bot]
afc36a633f Bump regex from 1.5.4 to 1.5.5
Bumps [regex](https://github.com/rust-lang/regex) from 1.5.4 to 1.5.5.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.5.4...1.5.5)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-07 09:00:43 +01:00
unknown
7275b273d4 Fix archive/directory check in platform.rs 2022-05-02 12:11:22 +01:00
andy.boot
a3e59f9c25 Update README.md 2022-03-05 22:02:09 +00:00
Adam Stephens
48bf656123 add aarch64 gnu and musl cross targets 2022-02-27 10:09:38 +00:00
andy.boot
fabb27908d Increment version 2022-02-26 11:47:06 +00:00
andy.boot
52aeeebe1f Improve help text 2022-02-26 11:47:06 +00:00
andy.boot
1e27288ec2 Fix test: Remove unwanted mod
This line caused the test_symlinks files to be run twice.
2022-02-26 11:10:26 +00:00
andy.boot
9f4a5daee6 Update library: Clap
Required several changes to main.rs as v3 had breaking changes.
2022-02-26 11:10:26 +00:00
andy.boot
27f0a015ef Update cargo packages 2022-02-26 11:10:26 +00:00
andy.boot
20d89bef91 Neaten code 2022-02-26 09:58:47 +00:00
andy.boot
469e6d0a69 Fix: Bug: names may be shortened unnecessarily
The code calculating the width of a row to use vs the width of the
terminal wasn't quite right.

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

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

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

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

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

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

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

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

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

https://github.com/bootandy/dust/issues/162
2021-07-29 08:54:32 +01:00
andy.boot
8fddb24165 Increment version 2021-07-19 14:09:21 +01:00
andy.boot
ed3902f07e Rename file: dirwalker -> dir_walker
Felt like this file was missing an '_'
2021-07-16 14:13:12 +01:00
andy.boot
f219a752d6 Refactor: Compress arguemnts to one object
Several arguments were passed around the dirwalker file. Compress them
into a single struct.
2021-07-16 14:13:12 +01:00
andy.boot
f6e36aba52 Feature: Re-introduce -x flag to limit filesystem
-x flag allows dust to limit itself to the current filesystem
2021-07-16 14:13:12 +01:00
andy.boot
c286b8ba97 Revert "README: Remove -x option"
This reverts commit 1d062cf683.
2021-07-16 14:13:12 +01:00
andy.boot
3dad7abfb8 Change size of softlinks to 0
Instead of the size of what they point at
2021-07-16 14:13:12 +01:00
andy.boot
42163abb73 Cargo: Remove num_cpus library. Ran update
Not needed since v0.6.0
Ran cargo update
2021-07-07 18:35:05 +01:00
andy.boot
8e0188c755 README: Update usage examples of dust 2021-07-07 18:35:05 +01:00
andy.boot
555d86206d ci: update versions of ubuntu 2021-06-23 10:05:48 +01:00
andy.boot
02392881c5 Update version 2021-06-23 09:40:58 +01:00
andy.boot
6f1175ef8d README: Add another tool to list of alternatives 2021-06-23 09:40:58 +01:00
andy.boot
1d062cf683 README: Remove -x option
This behaviour was removed in previous commit
2021-06-23 09:40:58 +01:00
andy.boot
be7a9181b2 Add alternative tools 2021-06-22 13:13:51 +01:00
andy.boot
9dcd4d0de4 Merge pull request #149 from bootandy/rewrite
Large Rewrite
2021-06-22 13:09:58 +01:00
andy.boot
00c7ce8f15 Large refactor. Use rayon, 10X performance boost
Code changes:
Removed ignore & channel crates. Using a single reciever thread to build
a hashmap to prevend duplicate inodes being reported gave a severe
performance penalty

Using rayon crate with some hand crafted file traversal has improved
performance aprox 10X

Behaviour changes:

Removed parameter 'limit by filesystem' - don't think this is used, and
I only added it as it was easy to add with the ignore crate.

Sym links will now not appear in the output tree unless using '-s'
'apparent-size' flag

Change behaviour of multiple args so that it unifies them and
compares them under one tree instead of treating them
individually: https://github.com/bootandy/dust/issues/136
2021-06-22 12:20:48 +01:00
andy.boot
c4a73d5921 Merge pull request #147 from QuarticCat/master
Beautify help messages
2021-06-08 10:40:34 +01:00
andy.boot
e1ffc92589 Merge branch 'master' into master 2021-06-08 10:24:31 +01:00
andy.boot
18729762ad Fix style for new version of clippy 2021-06-08 10:20:23 +01:00
ttay
8a499201de Updated crossbeam-channel to 0.5 and ran Cargo update 2021-06-08 10:11:11 +01:00
QuarticCat
551c5d3bfa Simplify conflict implementation 2021-06-05 21:09:31 +08:00
QuarticCat
15a867636f Add wrap_help feature to clap 2021-06-05 20:39:59 +08:00
andy.boot
1b3d0b2724 Update packages 2021-01-16 15:42:39 +00:00
andy.boot
d5fa7f0861 Increment version 2021-01-16 15:42:39 +00:00
andy.boot
e15cf0c42e [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:42:39 +00:00
andy.boot
1891de7fa3 Notes on how to publish 2021-01-16 15:42:39 +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
andy.boot
bc0e376c88 New Version 2020-02-19 20:22:54 +00:00
andy.boot
d7c602a2d2 Merge pull request #72 from bootandy/ab-redesign
Large redesign
2020-02-19 20:19:50 +00:00
andy.boot
603e6be7eb Large redesign
Use the whole width of the terminal assume width of 80 if none found

Show percentages in the final column. Show ASCII bars indicating usage
of the underlying directories in the space inbetween.

Display (height of terminal - 10) entries by default.

Reverse the output order so largest is at the bottom.

Break up tests. Change older tests to check real output of program.
2020-02-18 20:58:53 +00:00
andy.boot
efa469e12f Merge pull request #67 from bootandy/ab-simplify-inodes2
Simplify inodes & devices by removing Option
2020-02-10 22:11:35 +00:00
andy.boot
657858df16 Simplify inodes & devices by removing Option
This code now won't compile on none-(windows/unix family)
systems. [unix family includes mac]

Removing this method allows us to remove an Option and simplify the code
slightly
2020-02-09 14:33:46 +00:00
andy.boot
a8d700d530 Merge pull request #66 from bootandy/ab-refactor
Refactor code
2020-02-09 14:27:05 +00:00
andy.boot
c408d8887d Update docs for -X flag.
-X flag changed subtly with previous pull request. It now requires a
directory name and doesn't work on substrings.
2020-02-09 14:10:53 +00:00
andy.boot
be2250d241 Refactor: use if let instead of is_some 2020-02-09 14:10:53 +00:00
andy.boot
b6aa1378de Fix bug for devices and apparent size
If apparent_size was set and ignore files on other devices was set then
the latter flat would not work.

Fix this bug
2020-02-09 13:57:08 +00:00
andy.boot
2082141dfc Add tests for should_ignore_file function
function currently has a bug that is highlighted by second test
2020-02-09 13:57:08 +00:00
andy.boot
8a9b5e889d Refactor path depth calculation
Factor out duplicate code,
Add comment explaining why filter is necessary (thanks to rivy)
2020-02-09 13:57:08 +00:00
andy.boot
871b7e90d8 Merge pull request #64 from rivy/rf.path
Refactor ~ use Path/PathBuf instead of &str/String
2020-02-09 13:52:11 +00:00
andy.boot
edf300893c Increment version 2020-02-09 10:08:41 +00:00
Roy Ivy III
a3d8fc00e1 Refactor ~ use AsRef<Path> where possible 2020-02-03 16:56:50 -06:00
Roy Ivy III
9d4531d48b Refactor ~ use PathBuf instead of String 2020-02-03 16:56:47 -06:00
andy.boot
75e419e7ed Update README.md
Remove section on performance, I can not replicate this and don't want to cause controversy.
2020-02-03 20:54:19 +00:00
andy.boot
4a62fc5726 Update README.md
include dutree in alternatives
2020-02-03 20:52:12 +00:00
andy boot
d2b959fdcf Merge pull request #62 from rivy/add.gha
Fix windows compilation and testing
2020-02-03 20:31:37 +00:00
Roy Ivy III
36ebb1b2b0 refactor ~ strip_trailing_curdirs() -> normalize_path() (a more accurate descriptor) 2020-01-29 18:36:13 -06:00
Roy Ivy III
e126a01096 Tests ~ improve portability of tests for 'Fix ~ improve portability of path manipulation' 2020-01-29 18:36:13 -06:00
Roy Ivy III
7a38a26593 Fix ~ (WinAPI) must use Handle::from_path_any() if target path might be a directory
.# [why]

"If you use `from_path()` on a directory, then subsequent queries using that handle will fail."
Instead, use `from_path_any()` which supports opening files or directories.

ref: "Struct winapi_util::Handle" from <https://docs.rs/crate/winapi-util>
2020-01-29 18:36:13 -06:00
Roy Ivy III
1af6e1f757 Tests ~ simplify by ignoring instead of skipping tests for 'Tests ~ disable two symlink tests which may not be possible on 'windows'' 2020-01-29 18:36:13 -06:00
Roy Ivy III
affafcc5f2 Tests ~ simplify by ignoring instead of skipping tests for 'Tests ~ temporarily disable tests which vary by 'windows' hosts' 2020-01-29 18:36:13 -06:00
Roy Ivy III
26ef8c3e59 Tests ~ add test case to 'Fix ~ improve portability of path manipulation' 2020-01-29 18:36:13 -06:00
Roy Ivy III
0898ee6bf0 Maint/CI ~ temporarily disable non-working cargo tarpaulin test coverage 2020-01-29 18:36:13 -06:00
Roy Ivy III
5ac168868e Tests ~ temporarily disable tests which vary by 'windows' hosts 2020-01-29 18:36:13 -06:00
Roy Ivy III
684994ee11 Tests ~ disable two symlink tests which may not be possible on 'windows' 2020-01-29 18:36:12 -06:00
Roy Ivy III
416ad517fe Tests ~ fix windows testing 2020-01-29 18:36:12 -06:00
Roy Ivy III
7c34389aea Fix ~ improve portability of path manipulation 2020-01-29 18:36:12 -06:00
Roy Ivy III
18510130d8 Maint/CI ~ add GitHub-Actions CI (aka GHA) 2020-01-29 18:36:12 -06:00
andy boot
0bf8c914b7 Merge pull request #59 from Celti/exclude-multiple
Allow multiple --ignore-directory flags
2020-01-21 23:32:49 +00:00
Celti Burroughs
95888d5f31 Allow multiple --ignore-directory flags 2020-01-21 15:40:04 -07:00
andy boot
5d195e27cb Merge pull request #61 from lespea/fixWinBreaks
Fix get_metadata on windows
2020-01-21 22:06:00 +00:00
Adam Lesperance
0c7b05fec9 Fix get_metadata on windows
The MetadataExt functions are nightly-only right now so we need to call
the windows api ourselves to get the device number.  The winapi_util
package has a nice wrapper to do this for us.

The get_device function doesn't appear to be used anywhere so I removed
it.
2020-01-21 00:06:41 -06:00
andy boot
bc895879e6 Update README.md 2020-01-20 23:36:59 +00:00
andy boot
31f01c061a Update README.md 2020-01-19 09:55:51 +00:00
andy boot
17894e723c Merge pull request #55 from bootandy/ab-neaten
Ab fix clippy warnings, increment version number
2020-01-19 09:51:33 +00:00
andy boot
6141ddddea Merge pull request #54 from bootandy/ab-ignore-dir
Add option to ignore directories
2020-01-19 09:35:04 +00:00
andy.boot
352c0f7d90 Increment version number 2020-01-19 09:32:49 +00:00
andy.boot
842a8ec673 Clean up clippy warnings 2020-01-19 09:32:35 +00:00
andy.boot
d97edba041 Add option to ignore directories
https://github.com/bootandy/dust/issues/46

Add -X flag used to ignore any file or directory that matches the
provided substring.
This means that -X e will ignore any file or directory with an 'e' in
it.
2020-01-19 09:23:13 +00:00
andy boot
f64e0094f1 Merge pull request #53 from bootandy/single_cpu
Fix dust for single core machines
2020-01-18 23:06:28 +00:00
andy.boot
3d2477e554 Allow dust to run on single core machines
Force dust to use 2 threads if there is only 1 cpu
JWalk breaks if it tries to run on a single cpu

https://github.com/jessegrosjean/jwalk/issues/15

This is a hack because I can't figure out how to fix JWalk.
This code and the num_cpu crate can be removed when the bug is fixed

Also I have conflated cpus with cores while describing this issue. It is
very difficult to test as I don't have a single core machine.
2020-01-18 22:04:08 +00:00
andy.boot
350d695f7c Add num_cpus as Cargo dependency
Will be used to detect single core computers which crash Jwalk
2020-01-18 21:57:11 +00:00
andy boot
f8b8b8a788 Merge pull request #51 from bootandy/ab-exclude
Support excluding filesystems with -x
2020-01-18 21:54:41 +00:00
andy.boot
b9c27f9838 get_filesystem returns Result instead of option
Removes unwrap and returns a Result instead of panicing if an invalid
path is given.
Previously if the flag: '-x' was provided with an argument of an
invalid directory the code would crash here
2020-01-16 23:50:56 +00:00
andy.boot
5541df6a73 Refactor code
Reduce complexity of examine_dir.

No logic changes
2020-01-15 20:10:33 +00:00
andy.boot
bdc3d404ef Support excluding filesystems with -x
https://github.com/bootandy/dust/issues/50

Add optional -x flag to limit search to the current filesystem.

Add (untested) support for windows for the equivalent of inode and
device.
2020-01-15 19:51:16 +00:00
andy boot
f395a7d768 Merge pull request #49 from bootandy/ab-upgrade
Upgrade libraries. Fix assert_cli
2019-12-30 22:07:04 +00:00
andy.boot
a36eec6cae Increment version number 2019-12-30 21:40:30 +00:00
Adam Lesperance
38938e005e Fix colors on windows
* Initialize ansi_term if we're on windows
* Silence build warning on non-unix targets
* Update ansi-term which has windows specific fixes (and assert_cli while I'm at it)
2019-12-30 21:36:49 +00:00
andy.boot
da61b15715 Upgrade libraries. Fix assert_cli
New assert_cli library requires we cast the param passed to 'is()' as
it can no longer cast implicitly. I am guessing this is because it is
due to confusion between whether to cast to u8 or str
2019-12-30 21:23:53 +00:00
andy boot
0b22d0a977 Merge pull request #47 from bootandy/ab-bug-fix
Ab bug fix
2019-12-30 21:12:37 +00:00
andy.boot
356d14ac0f Hack fix around JWalk behaviour.
Jwalk returns '/' as a child of the current node which breaks things.
https://github.com/jessegrosjean/jwalk/issues/13
2019-12-23 15:51:12 +00:00
andy.boot
311bc45388 Verify that '/' is parent of everything
Bug could occur when run with '/' as it wasn't considered the parent of
'/usr' due to having the same number of '/'s in the name
2019-12-23 15:50:01 +00:00
andy.boot
ef66fb3938 Remove useless sort
The data coming in is already sorted. We should not need to sort it a
second time
2019-12-23 15:49:26 +00:00
andy.boot
b934445e04 Pin jwalk to dependency version
https://github.com/jessegrosjean/jwalk/issues/13
Having raised the above issue I don't want it fixed in a minor version
as I have added a work around
2019-12-23 15:48:38 +00:00
andy boot
a6839c020f Merge pull request #43 from bootandy/no_color
Add flag for No color, refactor tests
2019-12-08 23:50:58 +00:00
andy.boot
7e47d5b47a Increment version number 2019-12-08 23:36:48 +00:00
andy.boot
6a65570f3f Refactor tests
Make use of the 'is' more to test the entire output.

Add test for no_color mode.

Replace several calls to format_string with calls that build a string
directly. I feel format_string is becoming unweildly and this simplfies
the tests
2019-12-08 23:34:28 +00:00
andy.boot
a4ca78dbe4 Add option flag for no colors
https://github.com/bootandy/dust/issues/37
2019-12-08 14:46:45 +00:00
andy boot
bfbe8a57ae Merge pull request #42 from bootandy/cleanup
Cleanup code & fix issue
2019-12-07 09:16:51 +00:00
andy.boot
b4b73e45f3 Cargo update 2019-12-05 21:51:04 +00:00
andy.boot
78119aba0f Mark function as ignored by clippy
We don't want to collapse this if - it is easier to reason about this
way
2019-12-05 21:50:26 +00:00
andy.boot
5535478fe8 Fix substring bug
When one directory was a substring of another the directory would appear
as a subdirectory instead of a sibling

Fixed originally by this: 6e03dd77e6
Broken by this: db6c8a019d

Added integration test as this has bitten me before
2019-12-05 21:35:15 +00:00
andy.boot
7ba91a4a22 Rename variable
cpu -> threads. threads more accurately represents the number of threads
we will start to run
2019-12-04 20:49:45 +00:00
andy boot
79416fd5fc Merge pull request #40 from AdminXVII/parallel-walk
Use jwalk instead of walkdir: parallel walking for performance boost
2019-12-04 20:45:25 +00:00
Xavier L'Heureux
b66523cff3 Apply clippy lints 2019-12-03 18:34:47 -05:00
Xavier L'Heureux
19a41aa382 Add CLI option for the number of threads to spawn 2019-12-03 18:27:02 -05:00
Xavier L'Heureux
62ac9b623a Make sure to count the hidden directories 2019-11-26 08:31:52 -05:00
Xavier L'Heureux
bf28d42483 Update Performance section of README 2019-11-26 08:31:52 -05:00
Xavier L'Heureux
f8ce6c97bf Use more rusty patterns and preallocate enough space 2019-11-26 08:31:52 -05:00
Xavier L'Heureux
86b3cccaf6 perf(IO): use parallel walkdir (jwalk) for super faster traversal 2019-11-26 08:31:52 -05:00
andy boot
3c920431fa Merge pull request #38 from lespea/update_deps
Latest deps
2019-11-25 23:32:09 +00:00
Adam Lesperance
a1ece05af5 Testing deps should only be used when testing 2019-11-21 00:05:53 -06:00
Adam Lesperance
cef2c588b7 Latest deps 2019-11-20 23:05:57 -06:00
andy boot
7d8e498238 Merge pull request #36 from bootandy/try_to_fix_releases
Update secure key in Travis
2019-11-02 01:11:42 +00:00
andy boot
53c7a69dcb Merge pull request #35 from bootandy/fix-version
Provide version information with -V
2019-11-02 00:45:36 +00:00
andy.boot
9a9cbefd3d Provide version information with -V
clap does most of this for you - I had forgotten to wire it up
https://github.com/bootandy/dust/issues/33

increment build version
2019-11-02 00:42:57 +00:00
andy boot
224a2c6f25 Merge pull request #32 from mvertescher/update-edition
Upgrade to the 2018 edition
2019-10-27 10:39:04 +00:00
Matt Vertescher
99003cbba9 Upgrade to the 2018 edition 2019-10-26 15:19:07 -04:00
andy.boot
c83803b440 Add comment to travis file
So I don't forget how to do a release next time
2019-10-09 00:43:04 +01:00
andy.boot
a41862d799 Travis release: Read API key from variable
secure key was probably removed. Read from travis ci env variable
instead
2019-10-09 00:35:30 +01:00
andy.boot
6ab46d8471 Add reverse instructions 2019-10-08 22:46:52 +01:00
andy boot
c727eb2d11 Merge pull request #31 from bootandy/nodes_rev
Refactor & support reverse
2019-10-08 21:31:11 +01:00
andy.boot
0effaa7fd7 Increment version 2019-10-08 21:05:37 +01:00
andy.boot
25c50f88c4 cargo upgrade 2019-10-08 21:05:09 +01:00
andy.boot
0c19a66432 Add test for reverse flag 2019-10-08 20:57:44 +01:00
andy.boot
4cffc4370b Bring back the reverse flag
Following the large refactor on the previous commit, this commit fixes
the reverse functionality.

Depth detection moved into the tree building instead of being calculated
when drawing the tree to screen
2019-10-06 22:00:40 +01:00
andy.boot
db6c8a019d Massive refactor
WIP

Replace array of (string, int) pairs with tree of nodes.

A tree of nodes more accurately represents the underlying file structure
and hence is a better fit for the problem space.

Regression: Reverse doesn't work in this commit.

I suspect more methods can be simplifed and reduced.
2019-10-05 17:57:47 +01:00
andy.boot
e03094a4fa Add reverse flag
Pull several variables related to how output is printed
into DisplayData struct
2019-10-03 23:07:52 +01:00
andy.boot
1d9a56e025 A way of supporting reverse 2019-10-02 22:31:49 +01:00
andy.boot
ec2d9e19d4 Run format, introduce new function.
strip_end_slash_including_root will remove end slashes including the
root directory.

The root directory has been a long running problem because if we strip
the final slash we will run dust on no directory instead of the root.
2019-10-02 20:14:13 +01:00
andy.boot
9fbfcb275a pull out a method
(will be needed for reverse)
2019-10-02 20:06:09 +01:00
andy.boot
1c60d1e2ac Display: replace boolean with integer count
This will probably be useful when i refactor for the reverse mode
2019-10-02 19:54:00 +01:00
andy.boot
fd35734a94 Simplify string code, remove into 2019-10-02 19:48:57 +01:00
andy boot
c6f4ace2b6 Merge pull request #30 from bootandy/neaten
Neaten
2019-10-02 19:16:31 +01:00
Bob
d46b63fad8 Add detection of files which fail permission
Old code caught some file permission denied but not all.
2019-10-01 22:44:17 +01:00
Bob
872a49bb7d better var name 2019-10-01 22:35:29 +01:00
andy boot
04c6c204c3 Merge pull request #27 from bootandy/new2 2019-10-01 22:26:56 +01:00
Bob
7ac01e8166 Increment version number 2019-10-01 22:18:36 +01:00
Bob
2f7a88e8dc Fix issues from running on root directory
clean up: 80338f4

Fixes -d flag to work again. Add test to stop regression
2019-10-01 22:18:16 +01:00
Bob
2ca2cebdad New Cargo lock 2019-09-29 11:28:51 +01:00
bootandy
80338f4731 Fix running on root dir /
Fixes: https://github.com/bootandy/dust/issues/22

Allows code to run on the root directory
2019-07-04 00:03:33 +01:00
bootandy
d327bd2e68 Fix code to handle single dots in path
Before this fix adding a single dot to the end of a path would cause the
code to crash.
2019-07-02 00:56:43 +01:00
bootandy
7db6cf2f32 Add test to handle single dot in path 2019-07-02 00:54:58 +01:00
bootandy
76d0762c97 Add assert to stop infinite loops 2019-07-01 22:43:10 +01:00
bootandy
6e03dd77e6 Fix obscure display bug
When one directory was a substring of another with files in the files
could appear as children of the wrong directory

Fix: https://github.com/bootandy/dust/issues/25
2019-07-01 22:25:06 +01:00
bootandy
4906e9efda comment typos 2019-07-01 22:25:06 +01:00
bootandy
876609f2cb Obey new clippy
Clippy is like having a reviewer fix your dodgy code.
2019-07-01 22:25:06 +01:00
bootandy
12775db94b Update git ignore to ignore idea 2019-07-01 22:25:06 +01:00
bootandy
bfaf5ee173 Add bash line to ci script 2019-07-01 22:25:06 +01:00
bootandy
fd68330815 Update cargo lock 2019-07-01 22:25:06 +01:00
andy.boot
0bf4ebf554 Fix minor color bug
If we are not using RED then the Color should be the terminal default
not 7
2018-06-19 23:39:23 +01:00
andy boot
cab24f58d5 Merge pull request #20 from bootandy/refac
Refactor
2018-05-10 18:28:07 +01:00
andy.boot
b1b933d851 Refactor & Optimize use of depth
Code Refactored

Using Depth now prunes the tree below the displayable depth so display
calls finish quicker.
2018-05-10 17:36:56 +01:00
andy.boot
6a86e8befd Increment version number 2018-05-09 11:46:42 +01:00
andy boot
3fb91a6c29 Merge pull request #19 from bootandy/rm_dup_input
code to remove duplicate arguments
2018-05-09 11:44:38 +01:00
andy.boot
51561994c5 Break up display_node function slightly
Also: run rustformat
2018-05-09 11:28:23 +01:00
andy.boot
ce0e14bf00 Tweak output - the root node now has a: ─┬ 2018-05-09 10:58:32 +01:00
andy.boot
dd75ec4aa7 wip: code to remove duplicate arguments
Also handle case where an argument is a substring of another argument
2018-05-09 10:35:30 +01:00
andy boot
65cd42736a Update README.md 2018-05-09 09:43:30 +01:00
andy boot
4792e97177 Merge pull request #18 from bootandy/fixes
Fixes
2018-05-02 09:51:21 +01:00
bootandy
3e9f09e339 Remove unnecessary path & pathbuf code 2018-05-02 00:42:03 +01:00
bootandy
dba465a094 Tweak error message 2018-05-02 00:39:57 +01:00
bootandy
25d1ee7b43 Remove naked unwrap 2018-05-02 00:39:57 +01:00
bootandy
2556885622 Rust format 2018-05-02 00:39:57 +01:00
bootandy
8c088a7026 Fix: Passing a string into -n will no longer panic
fixes: #https://github.com/bootandy/dust/issues/16
2018-05-02 00:39:57 +01:00
bootandy
39db8b86fd Replace simple match with map_or 2018-05-01 14:38:34 +01:00
bootandy
c5830c5d00 Stop using target_os just use target_family
target_family unix covers linux and mac and wraps up the call for inodes
in a common interface.
2018-05-01 13:59:48 +01:00
bootandy
b68c450710 use eprintln! 2018-05-01 13:55:19 +01:00
andy.boot
6e2e5761d8 Increment version 2018-04-27 12:46:27 +01:00
andy boot
6842526d2c Merge pull request #15 from bootandy/depth2
Add support for -d depth flag
2018-04-27 12:28:15 +01:00
andy.boot
4ac85d7dc9 Simplify tests
dust sort is now more predictable as it orders first by size and second
by name.

Walkdir still gives different iteration orders on different systems so
all output is not entirely predictable
2018-04-27 11:35:47 +01:00
andy.boot
8170a07886 Increase default to 20 from 15
Clean up README a bit.
2018-04-27 10:23:20 +01:00
andy.boot
0f1f823736 Add long options to cmd line params 2018-04-27 10:15:30 +01:00
andy.boot
e6c777fb8b Add support for -d depth flag
Following a user request the option '-d N' allows a user to output N
levels of sub directories.

Fixed bug: so that trailing slashes are now removed.
2018-04-27 10:01:41 +01:00
andy boot
0bded9698a Merge pull request #14 from bootandy/fix_mac
Fix tests on mac
2018-04-24 17:09:30 +01:00
bootandy
c7f0ea59f0 Fix tests on mac
Macos does not appear to have a predictable iteration order of the files
2018-04-24 16:50:37 +01:00
andy boot
6d62cfb9ae Merge pull request #13 from bootandy/walkdir2
Rewrite to use walkdir instead of recursion
2018-04-24 15:41:51 +01:00
andy.boot
803934d84b By default only print last leaf of path
Fixes #9
https://github.com/bootandy/dust/issues/9

Instead of printing the all sub tree leaves only the last leaf is now
printed. See readme change for example

The flag '-p' was added to print the sub tree the old way
2018-04-24 14:54:11 +01:00
andy.boot
24c97ef92f Rewrite to use walkdir instead of recursion
Advised to use walkdir by burntsushi as using recursion on file systems
can blow the stack.

walkdir is slower but allows the code to be cleaner and more reliable

Also experimented with ignore but locking the hashmap resulted in
similar performance to walkdir but with much uglier code.
2018-04-24 14:53:47 +01:00
andy boot
270edf0a76 Update README.md 2018-04-18 13:49:05 +01:00
andy.boot
0c5b08e1d2 Change package name to du-dust
The binary is still called dust. Sadly dust was taken on crates.io.
This change should allow us to send this crate to crates.io
2018-04-18 13:40:52 +01:00
bootandy
35aef4c837 Add Cargo.lock 2018-04-16 23:09:04 +01:00
andy boot
95e7bb01eb Update README.md 2018-04-16 23:08:19 +01:00
andy boot
247e55788a Merge pull request #6 from Infinisil/unignore-lockfile
Don't ignore Cargo.lock
2018-04-16 22:54:07 +01:00
Silvan Mosberger
bcab66ab8b Don't ignore Cargo.lock
See https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries
2018-04-16 16:55:12 +02:00
andy boot
2c87a21da1 Merge pull request #5 from bootandy/refactor_orig
Refactor code
2018-04-07 17:17:54 +01:00
bootandy
74b9178017 Refactor code 2018-04-07 17:14:06 +01:00
andy boot
2eb1633b77 Update README.md 2018-04-07 11:53:55 +01:00
andy boot
3fd274ab36 Update README.md 2018-04-07 11:42:18 +01:00
andy boot
f3ab902811 Update README.md 2018-04-07 11:38:21 +01:00
bootandy
b3b1a867c4 Fix tests on CI
Files do not appear in predictable order - this order differs on
different versions of linux :-(.
2018-04-07 11:03:31 +01:00
bootandy
32561ecb18 Fix tests for linux 2018-04-07 10:23:47 +01:00
bootandy
2fe3943ed5 Update readme 2018-04-06 21:07:13 +01:00
bootandy
b8ad44b7f0 increment version 2018-04-06 20:49:22 +01:00
bootandy
1a34bc3198 Integration tests
Several tests for recursive dirs, hard & soft links.
Tests use the tempfile project.

Personally I find the tests hard to read. Am considering adding a
'--no-color' output option as this will make the tests much more
readable. (Currently we have to call format_string to get the matching
colors and if a test fails the diff is very hard to read).
2018-04-06 20:44:02 +01:00
bootandy
385ddb75e1 Minor code neatening 2018-04-06 20:35:00 +01:00
bootandy
5c6165da8a First integration test
This test needs neatening but it is the first example of a working
integration test
2018-04-05 14:45:04 +01:00
bootandy
f39b09e79c increment version (0.2.1 already tagged) 2018-04-05 14:45:04 +01:00
bootandy
778dbb44b3 Remove graying background
Remove background that gets grayer.
1) This looks funny on terminals that aren't black
2) Makes testing easier
2018-04-04 23:12:00 +01:00
bootandy
69c79d5f95 Update readme 2018-04-04 20:21:59 +01:00
bootandy
61ab0e8f96 Simplify build.
Just build mac and linux.
Remove appveyor file which was designed for windows.
2018-04-03 20:54:53 +01:00
bootandy
285fd62850 increment version 2018-04-03 19:53:37 +01:00
bootandy
6a63cbe1bc fix: Dust was supposed to take multiple dir args
Dust will now work when given multiple dirs. Before this fix it would
only show the largest dir.
2018-04-03 17:54:45 +01:00
bootandy
8d98171b82 Update README 2018-04-03 17:17:04 +01:00
bootandy
120b4e16e7 Squash Node and DirEnt objects into single object 2018-04-03 17:05:28 +01:00
bootandy
6198e3183f pull units variable out as constant 2018-04-03 16:37:29 +01:00
bootandy
ecf6c8f0e5 Refactor: Pull display code out to different file 2018-03-22 17:21:51 -04:00
andy boot
f14a9789f4 Merge pull request #4 from bootandy/blocks
Blocks
2018-03-22 16:37:05 -04:00
bootandy
4944d517f4 Apparent size mode: handle hard links.
If we are viewing apparent size then each hard linked file should be
counted. Not just the first one.
2018-03-22 14:36:17 -04:00
bootandy
cce656ab4c rm dead code 2018-03-22 14:30:30 -04:00
bootandy
03a517a310 block size is always 512 on rust 2018-03-22 14:29:59 -04:00
andy boot
3796c39ac9 Merge pull request #3 from nebkor/inodes_refactor
Broken into files, inode/dev changes from inodes branch taken
2018-03-22 12:34:12 -04:00
andy boot
0357fc5e71 Merge branch 'master' into inodes_refactor 2018-03-22 12:30:03 -04:00
Joe Ardent
aa3f411974 fixes blocksize error in get_blocksize().
Under Linux, MetadataExt::st_blocks() returns then number of 512B
blocks.

https://doc.rust-lang.org/1.23.0/std/os/linux/fs/trait.MetadataExt.html#tymethod.st_blocks
2018-03-22 00:34:14 -07:00
Joe Ardent
eb69ad19a0 break platform cfg code into utils submodule 2018-03-22 00:00:21 -07:00
Joe Ardent
c127580057 Bring over dev/inode pair changes from bootandy's repo 2018-03-21 23:47:37 -07:00
andy boot
c978c6cf93 Merge pull request #2 from bootandy/inodes
Inodes
2018-03-22 00:25:17 -04:00
bootandy
f72a67132c Remove commented out code 2018-03-21 19:50:13 -04:00
bootandy
4d1f881c17 fix: inodes are only unique with dev
First iteration adding dev to inodes to form a tuple to go into our
'have we seen this before' hashset.
inodes are not unique across partitions
2018-03-21 19:50:08 -04:00
Joe Ardent
e75a666a4c better display-children-finding code 2018-03-21 13:43:52 -07:00
Joe Ardent
617b0d2971 Merge branch 'nebkor' 2018-03-21 10:56:15 -07:00
Joe Ardent
8fa83e3836 First step of refactor done. 2018-03-21 10:55:03 -07:00
Joe Ardent
7fa2ce3434 start of refactor 2018-03-21 10:55:03 -07:00
bootandy
b0e971891e Fix basic message 2018-03-21 12:14:17 -04:00
andy boot
cef95fa415 Merge pull request #1 from nebkor/master
Minor cleanup, prettier output
2018-03-21 12:12:19 -04:00
Joe Ardent
381d286847 use accessor and creator methods for new types in lib 2018-03-21 00:09:23 -07:00
Joe Ardent
aa963defda update sample output with new box-drawing characters 2018-03-20 22:50:54 -07:00
Joe Ardent
0faf795284 First step of refactor done. 2018-03-20 22:40:53 -07:00
Joe Ardent
b7271b1da2 start of refactor 2018-03-20 22:09:29 -07:00
Joe Ardent
99f462f023 s/sibblings/siblings/g 2018-03-20 20:40:36 -07:00
Joe Ardent
1c80cbf28b Make printing slightly prettier 2018-03-20 19:23:36 -07:00
Joe Ardent
f802d7a6b4 quiet some clippy warnings 2018-03-20 19:23:36 -07:00
Joe Ardent
b4c6c68527 minor whitespace changes from rustfmt 2018-03-20 19:10:24 -07:00
bootandy
2c495364c7 None core OS: fix code bug 2018-03-19 16:11:55 -04:00
bootandy
32b653fe41 travis build file
no idea if this will work
2018-03-19 16:11:55 -04:00
42 changed files with 3854 additions and 368 deletions

347
.github/workflows/CICD.yml vendored Normal file
View File

@@ -0,0 +1,347 @@
name: CICD
# spell-checker:ignore CICD CODECOV MSVC MacOS Peltoche SHAs buildable clippy esac fakeroot gnueabihf halium libssl mkdir musl popd printf pushd rustfmt softprops toolchain
env:
PROJECT_NAME: dust
PROJECT_DESC: "du + rust = dust"
PROJECT_AUTH: "bootandy"
RUST_MIN_SRV: "1.31.0"
on: [push, pull_request]
jobs:
style:
name: Style
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest }
- { os: macos-latest }
- { os: windows-latest }
steps:
- uses: actions/checkout@v1
- name: Initialize workflow variables
id: vars
shell: bash
run: |
# 'windows-latest' `cargo fmt` is bugged for this project (see reasons @ GH:rust-lang/rustfmt #3324, #3590, #3688 ; waiting for repair)
JOB_DO_FORMAT_TESTING="true"
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:
toolchain: stable
override: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt, clippy
- name: "`fmt` testing"
if: steps.vars.outputs.JOB_DO_FORMAT_TESTING
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: "`clippy` testing"
if: success() || failure() # run regardless of prior step ("`fmt` testing") success/failure
uses: actions-rs/cargo@v1
with:
command: clippy
args: ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings
min_version:
name: MinSRV # Minimum supported rust version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_MIN_SRV }}
profile: minimal # minimal component installation (ie, no documentation)
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
build:
name: Build
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
# { os, target, cargo-options, features, use-cross, toolchain }
- {
os: ubuntu-latest,
target: aarch64-unknown-linux-gnu,
use-cross: use-cross,
}
- {
os: ubuntu-latest,
target: aarch64-unknown-linux-musl,
use-cross: use-cross,
}
- {
os: ubuntu-latest,
target: arm-unknown-linux-gnueabihf,
use-cross: use-cross,
}
- {
os: ubuntu-latest,
target: i686-unknown-linux-gnu,
use-cross: use-cross,
}
- {
os: ubuntu-latest,
target: i686-unknown-linux-musl,
use-cross: use-cross,
}
- {
os: ubuntu-latest,
target: x86_64-unknown-linux-gnu,
use-cross: use-cross,
}
- {
os: ubuntu-latest,
target: x86_64-unknown-linux-musl,
use-cross: use-cross,
}
- { os: macos-latest, target: x86_64-apple-darwin }
- { os: windows-latest, target: i686-pc-windows-gnu }
- { os: windows-latest, target: i686-pc-windows-msvc }
- { os: windows-latest, target: x86_64-pc-windows-gnu } ## !maint: [rivy; 2020-01-21] may break due to rust bug; follow possible solution from GH:rust-lang/rust#47048 (refs: GH:rust-lang/rust#47048 , GH:rust-lang/rust#53454 , GH:bike-barn/hermit#172 )
- { os: windows-latest, target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v1
- name: Install any prerequisites
shell: bash
run: |
case ${{ matrix.job.target }} in
arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install binutils-aarch64-linux-gnu ;;
esac
- name: Initialize workflow variables
id: vars
shell: bash
run: |
# toolchain
TOOLCHAIN="stable" ## default to "stable" toolchain
# * specify alternate TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: <https://github.com/rust-lang/rust/issues/47048>, <https://github.com/rust-lang/rust/issues/53454>, <https://github.com/rust-lang/cargo/issues/6754>)
case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
# * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN}
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
# staging directory
STAGING='_staging'
echo set-output name=STAGING::${STAGING}
echo ::set-output name=STAGING::${STAGING}
# determine EXE suffix
EXE_suffix="" ; case ${{ matrix.job.target }} in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
echo set-output name=EXE_suffix::${EXE_suffix}
echo ::set-output name=EXE_suffix::${EXE_suffix}
# parse commit reference info
REF_NAME=${GITHUB_REF#refs/*/}
unset REF_BRANCH ; case ${GITHUB_REF} in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac;
unset REF_TAG ; case ${GITHUB_REF} in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
REF_SHAS=${GITHUB_SHA:0:8}
echo set-output name=REF_NAME::${REF_NAME}
echo set-output name=REF_BRANCH::${REF_BRANCH}
echo set-output name=REF_TAG::${REF_TAG}
echo set-output name=REF_SHAS::${REF_SHAS}
echo ::set-output name=REF_NAME::${REF_NAME}
echo ::set-output name=REF_BRANCH::${REF_BRANCH}
echo ::set-output name=REF_TAG::${REF_TAG}
echo ::set-output name=REF_SHAS::${REF_SHAS}
# parse target
unset TARGET_ARCH ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) TARGET_ARCH=arm ;; aarch-*) TARGET_ARCH=aarch64 ;; i686-*) TARGET_ARCH=i686 ;; x86_64-*) TARGET_ARCH=x86_64 ;; esac;
echo set-output name=TARGET_ARCH::${TARGET_ARCH}
echo ::set-output name=TARGET_ARCH::${TARGET_ARCH}
unset TARGET_OS ; case ${{ matrix.job.target }} in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac;
echo set-output name=TARGET_OS::${TARGET_OS}
echo ::set-output name=TARGET_OS::${TARGET_OS}
# package name
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }}
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
echo set-output name=PKG_suffix::${PKG_suffix}
echo set-output name=PKG_BASENAME::${PKG_BASENAME}
echo set-output name=PKG_NAME::${PKG_NAME}
echo ::set-output name=PKG_suffix::${PKG_suffix}
echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
echo ::set-output name=PKG_NAME::${PKG_NAME}
# deployable tag? (ie, leading "vM" or "M"; M == version number)
unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
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}
echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS}
# # * `arm` cannot be tested on ubuntu-* hosts (b/c testing is currently primarily done via comparison of target outputs with built-in outputs and the `arm` target is not executable on the host)
JOB_DO_TESTING="true"
case ${{ matrix.job.target }} in arm-*|aarch64-*) unset JOB_DO_TESTING ;; esac;
echo set-output name=JOB_DO_TESTING::${JOB_DO_TESTING:-<empty>/false}
echo ::set-output name=JOB_DO_TESTING::${JOB_DO_TESTING}
# # * test only binary for arm-type targets
unset CARGO_TEST_OPTIONS
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-*|aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
# * strip executable?
STRIP="strip" ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;; aarch64-unknown-linux-musl) STRIP="" ;;esac;
echo set-output name=STRIP::${STRIP}
echo ::set-output name=STRIP::${STRIP}
- name: Create all needed build/work directories
shell: bash
run: |
mkdir -p '${{ steps.vars.outputs.STAGING }}'
mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
- name: rust toolchain ~ install
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
target: ${{ matrix.job.target }}
override: true
profile: minimal # minimal component installation (ie, no documentation)
- name: Info
shell: bash
run: |
gcc --version || true
rustup -V
rustup toolchain list
rustup default
cargo -V
rustc -V
- name: Build
uses: actions-rs/cargo@v1
with:
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
command: build
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
- name: Install cargo-deb
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-deb
if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl'
- name: Build deb
uses: actions-rs/cargo@v1
with:
command: deb
args: --no-build --target=${{ matrix.job.target }}
if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl'
- 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 }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
- name: Archive executable artifacts
uses: actions/upload-artifact@master
with:
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
- name: Archive deb artifacts
uses: actions/upload-artifact@master
with:
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}.deb
path: target/${{ matrix.job.target }}/debian
if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl'
- name: Package
shell: bash
run: |
# binary
cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
# `strip` binary (if needed)
if [ -n "${{ steps.vars.outputs.STRIP }}" ]; then "${{ steps.vars.outputs.STRIP }}" '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' ; fi
# README and LICENSE
cp README.md '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
cp LICENSE '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
# base compressed package
pushd '${{ steps.vars.outputs.STAGING }}/' >/dev/null
case ${{ matrix.job.target }} in
*-pc-windows-*) 7z -y a '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* | tail -2 ;;
*) tar czf '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* ;;
esac;
popd >/dev/null
- name: Publish
uses: softprops/action-gh-release@v1
if: steps.vars.outputs.DEPLOY
with:
files: |
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
target/${{ matrix.job.target }}/debian/*.deb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
## fix! [rivy; 2020-22-01] `cargo tarpaulin` is unable to test this repo at the moment; alternate recipe or another testing framework?
# coverage:
# name: Code Coverage
# runs-on: ${{ matrix.job.os }}
# strategy:
# fail-fast: true
# matrix:
# # job: [ { os: ubuntu-latest }, { os: macos-latest }, { os: windows-latest } ]
# job: [ { os: ubuntu-latest } ] ## cargo-tarpaulin is currently only available on linux
# steps:
# - uses: actions/checkout@v1
# # - name: Reattach HEAD ## may be needed for accurate code coverage info
# # run: git checkout ${{ github.head_ref }}
# - name: Initialize workflow variables
# id: vars
# shell: bash
# run: |
# # staging directory
# STAGING='_staging'
# echo set-output name=STAGING::${STAGING}
# echo ::set-output name=STAGING::${STAGING}
# # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
# unset HAS_CODECOV_TOKEN
# if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi
# echo set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN}
# echo ::set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN}
# env:
# CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"
# - name: Create all needed build/work directories
# shell: bash
# run: |
# mkdir -p '${{ steps.vars.outputs.STAGING }}/work'
# - name: Install required packages
# run: |
# sudo apt-get -y install libssl-dev
# pushd '${{ steps.vars.outputs.STAGING }}/work' >/dev/null
# wget --no-verbose https://github.com/xd009642/tarpaulin/releases/download/0.9.3/cargo-tarpaulin-0.9.3-travis.tar.gz
# tar xf cargo-tarpaulin-0.9.3-travis.tar.gz
# cp cargo-tarpaulin "$(dirname -- "$(which cargo)")"/
# popd >/dev/null
# - name: Generate coverage
# run: |
# cargo tarpaulin --out Xml
# - name: Upload coverage results (CodeCov.io)
# # CODECOV_TOKEN (aka, "Repository Upload Token" for REPO from CodeCov.io) ## set via REPO/Settings/Secrets
# # if: secrets.CODECOV_TOKEN (not supported {yet?}; see <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
# run: |
# # CodeCov.io
# cargo tarpaulin --out Xml
# bash <(curl -s https://codecov.io/bash)
# env:
# CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"

6
.gitignore vendored
View File

@@ -2,10 +2,8 @@
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
*.swp
.vscode/*
*.idea/*

657
Cargo.lock generated Normal file
View File

@@ -0,0 +1,657 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "assert_cmd"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe"
dependencies = [
"bstr",
"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",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_complete"
version = "3.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4179da71abd56c26b54dd0c248cc081c1f43b0a1a7e8448e28e57a29baa993d"
dependencies = [
"clap",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "config-file"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51e72c150781d2c7d4cbcb0b803277caaa80476786994a62961a8f1010dafb"
dependencies = [
"serde",
"thiserror",
"toml",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"once_cell",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "directories"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[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.8.3"
dependencies = [
"ansi_term",
"assert_cmd",
"clap",
"clap_complete",
"config-file",
"directories",
"lscolors",
"rayon",
"regex",
"serde",
"stfu8",
"tempfile",
"terminal_size",
"thousands",
"unicode-width",
"winapi-util",
]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "fastrand"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [
"instant",
]
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "lscolors"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
dependencies = [
"ansi_term",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
[[package]]
name = "os_str_bytes"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]]
name = "predicates"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c"
dependencies = [
"difflib",
"itertools",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb"
[[package]]
name = "predicates-tree"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032"
dependencies = [
"predicates-core",
"termtree",
]
[[package]]
name = "proc-macro2"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "stfu8"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "019f0c664fd85d5a87dcfb62b40b691055392a35a6e59f4df83d4b770db7e876"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "termtree"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b"
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thousands"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "unicode-ident"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"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.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -1,8 +1,81 @@
[package]
name = "du-dust"
description = "A more intuitive version of du"
version = "0.8.3"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2021"
readme = "README.md"
documentation = "https://github.com/bootandy/dust"
homepage = "https://github.com/bootandy/dust"
repository = "https://github.com/bootandy/dust"
keywords = ["du", "command-line", "disk", "disk-usage"]
categories = ["command-line-utilities"]
license = "Apache-2.0"
[badges]
travis-ci = { repository = "https://travis-ci.org/bootandy/dust" }
[[bin]]
name = "dust"
version = "0.1.0"
authors = ["bootandy <bootandy@gmail.com>"]
path = "src/main.rs"
[profile.release]
codegen-units = 1
lto = true
strip = true
[dependencies]
ansi_term = "0.11"
clap = "2.31"
ansi_term = "0.12"
clap = "3.2.17"
lscolors = "0.7"
terminal_size = "0.1"
unicode-width = "0.1"
rayon = "1"
thousands = "0.2"
stfu8 = "0.2"
regex = "1"
config-file = "0.2"
serde = { version = "1.0", features = ["derive"] }
directories = "4"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"
[dev-dependencies]
assert_cmd = "1"
tempfile = "=3"
[build-dependencies]
clap = "3.2.17"
clap_complete = "3.2.4"
[[test]]
name = "integration"
path = "tests/tests.rs"
[package.metadata.deb]
section = "utils"
assets = [
[
"target/release/dust",
"usr/bin/",
"755",
],
[
"LICENSE",
"usr/share/doc/du-dust/",
"644",
],
[
"README.md",
"usr/share/doc/du-dust/README",
"644",
],
]
extended-description = """\
Dust is meant to give you an instant overview of which directories are using
disk space without requiring sort or head. Dust will print a maximum of one
'Did not have permissions message'.
"""

101
README.md
View File

@@ -1,39 +1,86 @@
[![Build Status](https://travis-ci.org/bootandy/dust.svg?branch=master)](https://travis-ci.org/bootandy/dust)
# Dust
du + rust = dust. A rust alternative to du
Unlike du, dust is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust does not count file system blocks; it uses file sizes instead. Dust will print a maximum of 1 'Did not have permissions message'.
du + rust = dust. Like du but more intuitive.
# Why
Dust will list the 15 biggest sub directories 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
Because I want an easy way to see where my disk is being used.
# Demo
![Example](media/snap.png)
## 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`
#### 🍺 Homebrew (Mac OS)
- `brew install dust`
#### 🍺 Homebrew (Linux)
- `brew tap tgotwig/linux-dust && brew install dust`
#### [Pacstall](https://github.com/pacstall/pacstall) (Debian/Ubuntu)
- `pacstall -I dust-bin`
#### Windows:
- Windows GNU version - works
- Windows MSVC - requires: [VCRUNTIME140.dll](https://docs.microsoft.com/en-gb/cpp/windows/latest-supported-vc-redist?view=msvc-170)
#### 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 one 'Did not have permissions message'.
Dust will list a slightly-less-than-the-terminal-height number of the biggest subdirectories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest subdirectories will be colored.
The different colors on the bars: These represent the combined tree hierarchy & disk usage. The shades of grey are used to indicate which parent folder a subfolder belongs to. For instance, look at the above screenshot. `.steam` is a folder taking 44% of the space. From the `.steam` bar is a light grey line that goes up. All these folders are inside `.steam` so if you delete `.steam` all that stuff will be gone too.
## Usage
```
Usage: dust
Usage: dust <dir>
Usage: dust -n 30 <dir> (Shows 30 directories not 15)
```
Usage: dust <dir> <another_dir> <and_more>
Usage: dust -p (full-path - Show fullpath of the subdirectories)
Usage: dust -s (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 (Shows 30 directories instead of the default [default is terminal height])
Usage: dust -d 3 (Shows 3 levels of subdirectories)
Usage: dust -r (reverse order of output)
Usage: dust -H (si print sizes in powers of 1000 instead of 1024)
Usage: dust -X ignore (ignore all files and directories with the name 'ignore')
Usage: dust -x (Only show directories on the same filesystem)
Usage: dust -b (Do not show percentages or draw ASCII bars)
Usage: dust -i (Do not show hidden files)
Usage: dust -c (No colors [monochrome])
Usage: dust -f (Count files instead of diskspace)
Usage: dust -t (Group by filetype)
Usage: dust -z 10M (min-size, Only include files larger than 10M)
Usage: dust -e regex (Only include files matching this regex (eg dust -e "\.png$" would match png files))
Usage: dust -v regex (Exclude files matching this regex (eg dust -v "\.png$" would ignore png files))
```
dust .
161M .
160M └── ./target
123M ├── ./target/debug
83M │ ├── ./target/debug/deps
16M │ │ ├── ./target/debug/deps/libclap-82e6176feef5d4b7.rlib
8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM
8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM/Contents
8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM/Contents/Resources
27M │ ├── ./target/debug/incremental
12M │ └── ./target/debug/build
20M ├── ./target/x86_64-apple-darwin
20M │ └── ./target/x86_64-apple-darwin/debug
20M │ └── ./target/x86_64-apple-darwin/debug/deps
16M │ └── ./target/x86_64-apple-darwin/debug/deps/libclap-7e3f8513c52cd558.rlib
16M └── ./target/release
13M └── ./target/release/deps
```
Performance: dust is currently about 4 times slower than du.
## Alternatives
- [NCDU](https://dev.yorhel.nl/ncdu)
- [dutree](https://github.com/nachoparker/dutree)
- [dua](https://github.com/Byron/dua-cli/)
- [pdu](https://github.com/KSXGitHub/parallel-disk-usage)
- [dirstat-rs](https://github.com/scullionw/dirstat-rs)
- du -d 1 -h | sort -h
Note: Apparent-size is calculated slightly differently in dust to gdu. In dust each hard link is counted as using file_length space. In gdu only the first entry is counted.

18
build.rs Normal file
View File

@@ -0,0 +1,18 @@
use clap_complete::{generate_to, shells::*};
use std::io::Error;
include!("src/cli.rs");
fn main() -> Result<(), Error> {
let outdir = "completions";
let app_name = "dust";
let mut cmd = build_cli();
generate_to(Bash, &mut cmd, app_name, outdir)?;
generate_to(Zsh, &mut cmd, app_name, outdir)?;
generate_to(Fish, &mut cmd, app_name, outdir)?;
generate_to(PowerShell, &mut cmd, app_name, outdir)?;
generate_to(Elvish, &mut cmd, app_name, outdir)?;
Ok(())
}

23
ci/before_deploy.ps1 Normal file
View File

@@ -0,0 +1,23 @@
# This script takes care of packaging the build artifacts that will go in the
# release zipfile
$SRC_DIR = $PWD.Path
$STAGE = [System.Guid]::NewGuid().ToString()
Set-Location $ENV:Temp
New-Item -Type Directory -Name $STAGE
Set-Location $STAGE
$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip"
# TODO Update this to package the right artifacts
Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\dust" '.\'
7z a "$ZIP" *
Push-AppveyorArtifact "$ZIP"
Remove-Item *.* -Force
Set-Location ..
Remove-Item $STAGE
Set-Location $SRC_DIR

34
ci/before_deploy.sh Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# This script takes care of building your crate and packaging it for release
set -ex
main() {
local src=$(pwd) \
stage=
case $TRAVIS_OS_NAME in
linux)
stage=$(mktemp -d)
;;
osx)
stage=$(mktemp -d -t tmp)
;;
esac
test -f Cargo.lock || cargo generate-lockfile
# TODO Update this to build the artifacts that matter to you
cross rustc --bin dust --target $TARGET --release -- -C lto
# TODO Update this to package the right artifacts
cp target/$TARGET/release/dust $stage/
cd $stage
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
cd $src
rm -rf $stage
}
main

10
ci/how2publish.txt Normal file
View File

@@ -0,0 +1,10 @@
# ----------- To do a release ---------
# edit version in cargo.toml
# tag a commit and push (increment version in Cargo.toml first):
# git tag v0.4.5
# git push origin v0.4.5
# cargo publish to put it in crates.io
# To install locally [Do before pushing it]
#cargo install --path .

28
ci/install.sh Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -ex
main() {
local target=
if [ $TRAVIS_OS_NAME = linux ]; then
target=x86_64-unknown-linux-musl
sort=sort
else
target=x86_64-apple-darwin
sort=gsort # for `sort --sort-version`, from brew's coreutils.
fi
# This fetches latest stable release
local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
| cut -d/ -f3 \
| grep -E '^v[0.1.0-9.]+$' \
| $sort --version-sort \
| tail -n1)
curl -LSfs https://japaric.github.io/trust/install.sh | \
sh -s -- \
--force \
--git japaric/cross \
--tag $tag \
--target $target
}
main

25
ci/script.sh Normal file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# This script takes care of testing your crate
set -ex
# TODO This is the "test phase", tweak it as you see fit
main() {
cross build --target $TARGET
cross build --target $TARGET --release
if [ ! -z $DISABLE_TESTS ]; then
return
fi
cross test --target $TARGET
cross test --target $TARGET --release
cross run --target $TARGET
cross run --target $TARGET --release
}
# we don't run the "test phase" when doing deploys
if [ -z $TRAVIS_TAG ]; then
main
fi

67
completions/_dust Normal file
View File

@@ -0,0 +1,67 @@
#compdef dust
autoload -U is-at-least
_dust() {
typeset -A opt_args
typeset -a _arguments_options
local ret=1
if is-at-least 5.2; then
_arguments_options=(-s -S -C)
else
_arguments_options=(-s -C)
fi
local context curcontext="$curcontext" state line
_arguments "${_arguments_options[@]}" \
'-d+[Depth to show]: : ' \
'--depth=[Depth to show]: : ' \
'-n+[Number of lines of output to show. (Default is terminal_height - 10)]: : ' \
'--number-of-lines=[Number of lines of output to show. (Default is terminal_height - 10)]: : ' \
'*-X+[Exclude any file or directory with this name]: : ' \
'*--ignore-directory=[Exclude any file or directory with this name]: : ' \
'-z+[Minimum size file to include in output]: : ' \
'--min-size=[Minimum size file to include in output]: : ' \
'(-e --filter -t --file_types)*-v+[Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ]: : ' \
'(-e --filter -t --file_types)*--invert-filter=[Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ]: : ' \
'(-t --file_types)*-e+[Only include filepaths matching this regex. For png files type: -e "\\.png$" ]: : ' \
'(-t --file_types)*--filter=[Only include filepaths matching this regex. For png files type: -e "\\.png$" ]: : ' \
'-w+[Specify width of output overriding the auto detection of terminal width]: : ' \
'--terminal_width=[Specify width of output overriding the auto detection of terminal width]: : ' \
'-h[Print help information]' \
'--help[Print help information]' \
'-V[Print version information]' \
'--version[Print version information]' \
'-p[Subdirectories will not have their path shortened]' \
'--full-paths[Subdirectories will not have their path shortened]' \
'-x[Only count the files and directories on the same filesystem as the supplied directory]' \
'--limit-filesystem[Only count the files and directories on the same filesystem as the supplied directory]' \
'-s[Use file length instead of blocks]' \
'--apparent-size[Use file length instead of blocks]' \
'-r[Print tree upside down (biggest highest)]' \
'--reverse[Print tree upside down (biggest highest)]' \
'-c[No colors will be printed (Useful for commands like: watch)]' \
'--no-colors[No colors will be printed (Useful for commands like: watch)]' \
'-b[No percent bars or percentages will be displayed]' \
'--no-percent-bars[No percent bars or percentages will be displayed]' \
'--skip-total[No total row will be displayed]' \
'-f[Directory '\''size'\'' is number of child files/dirs not disk size]' \
'--filecount[Directory '\''size'\'' is number of child files/dirs not disk size]' \
'-i[Do not display hidden files]' \
'--ignore_hidden[Do not display hidden files]' \
'(-d --depth)-t[show only these file types]' \
'(-d --depth)--file_types[show only these file types]' \
'-H[print sizes in powers of 1000 (e.g., 1.1G)]' \
'--si[print sizes in powers of 1000 (e.g., 1.1G)]' \
'*::inputs:' \
&& ret=0
}
(( $+functions[_dust_commands] )) ||
_dust_commands() {
local commands; commands=()
_describe -t commands 'dust commands' commands "$@"
}
_dust "$@"

69
completions/_dust.ps1 Normal file
View File

@@ -0,0 +1,69 @@
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$commandElements = $commandAst.CommandElements
$command = @(
'dust'
for ($i = 1; $i -lt $commandElements.Count; $i++) {
$element = $commandElements[$i]
if ($element -isnot [StringConstantExpressionAst] -or
$element.StringConstantType -ne [StringConstantType]::BareWord -or
$element.Value.StartsWith('-') -or
$element.Value -eq $wordToComplete) {
break
}
$element.Value
}) -join ';'
$completions = @(switch ($command) {
'dust' {
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Depth to show')
[CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Depth to show')
[CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)')
[CompletionResult]::new('--number-of-lines', 'number-of-lines', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)')
[CompletionResult]::new('-X', 'X', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this name')
[CompletionResult]::new('--ignore-directory', 'ignore-directory', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this name')
[CompletionResult]::new('-z', 'z', [CompletionResultType]::ParameterName, 'Minimum size file to include in output')
[CompletionResult]::new('--min-size', 'min-size', [CompletionResultType]::ParameterName, 'Minimum size file to include in output')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
[CompletionResult]::new('--invert-filter', 'invert-filter', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
[CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$" ')
[CompletionResult]::new('--filter', 'filter', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$" ')
[CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
[CompletionResult]::new('--terminal_width', 'terminal_width', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened')
[CompletionResult]::new('--full-paths', 'full-paths', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened')
[CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory')
[CompletionResult]::new('--limit-filesystem', 'limit-filesystem', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Use file length instead of blocks')
[CompletionResult]::new('--apparent-size', 'apparent-size', [CompletionResultType]::ParameterName, 'Use file length instead of blocks')
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)')
[CompletionResult]::new('--reverse', 'reverse', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)')
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
[CompletionResult]::new('--no-colors', 'no-colors', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
[CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed')
[CompletionResult]::new('--no-percent-bars', 'no-percent-bars', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed')
[CompletionResult]::new('--skip-total', 'skip-total', [CompletionResultType]::ParameterName, 'No total row will be displayed')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files/dirs not disk size')
[CompletionResult]::new('--filecount', 'filecount', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files/dirs not disk size')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Do not display hidden files')
[CompletionResult]::new('--ignore_hidden', 'ignore_hidden', [CompletionResultType]::ParameterName, 'Do not display hidden files')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'show only these file types')
[CompletionResult]::new('--file_types', 'file_types', [CompletionResultType]::ParameterName, 'show only these file types')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'print sizes in powers of 1000 (e.g., 1.1G)')
[CompletionResult]::new('--si', 'si', [CompletionResultType]::ParameterName, 'print sizes in powers of 1000 (e.g., 1.1G)')
break
}
})
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
Sort-Object -Property ListItemText
}

94
completions/dust.bash Normal file
View File

@@ -0,0 +1,94 @@
_dust() {
local i cur prev opts cmds
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
cmd=""
opts=""
for i in ${COMP_WORDS[@]}
do
case "${i}" in
"$1")
cmd="dust"
;;
*)
;;
esac
done
case "${cmd}" in
dust)
opts="-h -V -d -n -p -X -x -s -r -c -b -z -f -i -v -e -t -w -H --help --version --depth --number-of-lines --full-paths --ignore-directory --limit-filesystem --apparent-size --reverse --no-colors --no-percent-bars --min-size --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --si <inputs>..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--depth)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-d)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--number-of-lines)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-n)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--ignore-directory)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-X)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--min-size)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-z)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--invert-filter)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-v)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--filter)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-e)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--terminal_width)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-w)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
esac
}
complete -F _dust -o bashdefault -o default dust

63
completions/dust.elv Normal file
View File

@@ -0,0 +1,63 @@
use builtin;
use str;
set edit:completion:arg-completer[dust] = {|@words|
fn spaces {|n|
builtin:repeat $n ' ' | str:join ''
}
fn cand {|text desc|
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
}
var command = 'dust'
for word $words[1..-1] {
if (str:has-prefix $word '-') {
break
}
set command = $command';'$word
}
var completions = [
&'dust'= {
cand -d 'Depth to show'
cand --depth 'Depth to show'
cand -n 'Number of lines of output to show. (Default is terminal_height - 10)'
cand --number-of-lines 'Number of lines of output to show. (Default is terminal_height - 10)'
cand -X 'Exclude any file or directory with this name'
cand --ignore-directory 'Exclude any file or directory with this name'
cand -z 'Minimum size file to include in output'
cand --min-size 'Minimum size file to include in output'
cand -v 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" '
cand --invert-filter 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" '
cand -e 'Only include filepaths matching this regex. For png files type: -e "\.png$" '
cand --filter 'Only include filepaths matching this regex. For png files type: -e "\.png$" '
cand -w 'Specify width of output overriding the auto detection of terminal width'
cand --terminal_width 'Specify width of output overriding the auto detection of terminal width'
cand -h 'Print help information'
cand --help 'Print help information'
cand -V 'Print version information'
cand --version 'Print version information'
cand -p 'Subdirectories will not have their path shortened'
cand --full-paths 'Subdirectories will not have their path shortened'
cand -x 'Only count the files and directories on the same filesystem as the supplied directory'
cand --limit-filesystem 'Only count the files and directories on the same filesystem as the supplied directory'
cand -s 'Use file length instead of blocks'
cand --apparent-size 'Use file length instead of blocks'
cand -r 'Print tree upside down (biggest highest)'
cand --reverse 'Print tree upside down (biggest highest)'
cand -c 'No colors will be printed (Useful for commands like: watch)'
cand --no-colors 'No colors will be printed (Useful for commands like: watch)'
cand -b 'No percent bars or percentages will be displayed'
cand --no-percent-bars 'No percent bars or percentages will be displayed'
cand --skip-total 'No total row will be displayed'
cand -f 'Directory ''size'' is number of child files/dirs not disk size'
cand --filecount 'Directory ''size'' is number of child files/dirs not disk size'
cand -i 'Do not display hidden files'
cand --ignore_hidden 'Do not display hidden files'
cand -t 'show only these file types'
cand --file_types 'show only these file types'
cand -H 'print sizes in powers of 1000 (e.g., 1.1G)'
cand --si 'print sizes in powers of 1000 (e.g., 1.1G)'
}
]
$completions[$command]
}

20
completions/dust.fish Normal file
View File

@@ -0,0 +1,20 @@
complete -c dust -s d -l depth -d 'Depth to show' -r
complete -c dust -s n -l number-of-lines -d 'Number of lines of output to show. (Default is terminal_height - 10)' -r
complete -c dust -s X -l ignore-directory -d 'Exclude any file or directory with this name' -r
complete -c dust -s z -l min-size -d 'Minimum size file to include in output' -r
complete -c dust -s v -l invert-filter -d 'Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ' -r
complete -c dust -s e -l filter -d 'Only include filepaths matching this regex. For png files type: -e "\\.png$" ' -r
complete -c dust -s w -l terminal_width -d 'Specify width of output overriding the auto detection of terminal width' -r
complete -c dust -s h -l help -d 'Print help information'
complete -c dust -s V -l version -d 'Print version information'
complete -c dust -s p -l full-paths -d 'Subdirectories will not have their path shortened'
complete -c dust -s x -l limit-filesystem -d 'Only count the files and directories on the same filesystem as the supplied directory'
complete -c dust -s s -l apparent-size -d 'Use file length instead of blocks'
complete -c dust -s r -l reverse -d 'Print tree upside down (biggest highest)'
complete -c dust -s c -l no-colors -d 'No colors will be printed (Useful for commands like: watch)'
complete -c dust -s b -l no-percent-bars -d 'No percent bars or percentages will be displayed'
complete -c dust -l skip-total -d 'No total row will be displayed'
complete -c dust -s f -l filecount -d 'Directory \'size\' is number of child files/dirs not disk size'
complete -c dust -s i -l ignore_hidden -d 'Do not display hidden files'
complete -c dust -s t -l file_types -d 'show only these file types'
complete -c dust -s H -l si -d 'print sizes in powers of 1000 (e.g., 1.1G)'

13
config/config.toml Normal file
View File

@@ -0,0 +1,13 @@
# Sample Config file, works with toml and yaml
# Place in either:
# ~/.config/dust/config.toml
# ~/.dust.toml
reverse=true
display-full-paths=true
display-apparent-size=true
no-colors=true
no-bars=true
skip-total=true
ignore-hidden=true
iso=true

BIN
media/snap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

135
src/cli.rs Normal file
View File

@@ -0,0 +1,135 @@
use clap::{Arg, Command};
pub fn build_cli() -> Command<'static> {
Command::new("Dust")
.about("Like du but more intuitive")
.version(env!("CARGO_PKG_VERSION"))
.trailing_var_arg(true)
.arg(
Arg::new("depth")
.short('d')
.long("depth")
.help("Depth to show")
.takes_value(true)
)
.arg(
Arg::new("number_of_lines")
.short('n')
.long("number-of-lines")
.help("Number of lines of output to show. (Default is terminal_height - 10)")
.takes_value(true)
)
.arg(
Arg::new("display_full_paths")
.short('p')
.long("full-paths")
.help("Subdirectories will not have their path shortened"),
)
.arg(
Arg::new("ignore_directory")
.short('X')
.long("ignore-directory")
.takes_value(true)
.number_of_values(1)
.multiple_occurrences(true)
.help("Exclude any file or directory with this name"),
)
.arg(
Arg::new("limit_filesystem")
.short('x')
.long("limit-filesystem")
.help("Only count the files and directories on the same filesystem as the supplied directory"),
)
.arg(
Arg::new("display_apparent_size")
.short('s')
.long("apparent-size")
.help("Use file length instead of blocks"),
)
.arg(
Arg::new("reverse")
.short('r')
.long("reverse")
.help("Print tree upside down (biggest highest)"),
)
.arg(
Arg::new("no_colors")
.short('c')
.long("no-colors")
.help("No colors will be printed (Useful for commands like: watch)"),
)
.arg(
Arg::new("no_bars")
.short('b')
.long("no-percent-bars")
.help("No percent bars or percentages will be displayed"),
)
.arg(
Arg::new("min_size")
.short('z')
.long("min-size")
.takes_value(true)
.number_of_values(1)
.help("Minimum size file to include in output"),
)
.arg(
Arg::new("skip_total")
.long("skip-total")
.help("No total row will be displayed"),
)
.arg(
Arg::new("by_filecount")
.short('f')
.long("filecount")
.help("Directory 'size' is number of child files/dirs not disk size"),
)
.arg(
Arg::new("ignore_hidden")
.short('i') // Do not use 'h' this is used by 'help'
.long("ignore_hidden")
.help("Do not display hidden files"),
)
.arg(
Arg::new("invert_filter")
.short('v')
.long("invert-filter")
.takes_value(true)
.number_of_values(1)
.multiple_occurrences(true)
.conflicts_with("filter")
.conflicts_with("types")
.help("Exclude filepaths matching this regex. To ignore png files type: -v \"\\.png$\" "),
)
.arg(
Arg::new("filter")
.short('e')
.long("filter")
.takes_value(true)
.number_of_values(1)
.multiple_occurrences(true)
.conflicts_with("types")
.help("Only include filepaths matching this regex. For png files type: -e \"\\.png$\" "),
)
.arg(
Arg::new("types")
.short('t')
.long("file_types")
.conflicts_with("depth")
.help("show only these file types"),
)
.arg(
Arg::new("width")
.short('w')
.long("terminal_width")
.takes_value(true)
.number_of_values(1)
.help("Specify width of output overriding the auto detection of terminal width"),
)
.arg(
Arg::new("iso")
.short('H')
.long("si")
.help("print sizes in powers of 1000 (e.g., 1.1G)")
)
.arg(Arg::new("inputs").multiple_occurrences(true).default_value("."))
}

148
src/config.rs Normal file
View File

@@ -0,0 +1,148 @@
use clap::ArgMatches;
use config_file::FromConfigFile;
use serde::Deserialize;
use std::path::Path;
use std::path::PathBuf;
use crate::display::UNITS;
#[derive(Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
#[serde(deny_unknown_fields)]
pub struct Config {
pub display_full_paths: Option<bool>,
pub display_apparent_size: Option<bool>,
pub reverse: Option<bool>,
pub no_colors: Option<bool>,
pub no_bars: Option<bool>,
pub skip_total: Option<bool>,
pub ignore_hidden: Option<bool>,
pub iso: Option<bool>,
pub min_size: Option<String>,
}
impl Config {
pub fn get_no_colors(&self, options: &ArgMatches) -> bool {
Some(true) == self.no_colors || options.is_present("no_colors")
}
pub fn get_apparent_size(&self, options: &ArgMatches) -> bool {
Some(true) == self.display_apparent_size || options.is_present("display_apparent_size")
}
pub fn get_ignore_hidden(&self, options: &ArgMatches) -> bool {
Some(true) == self.ignore_hidden || options.is_present("ignore_hidden")
}
pub fn get_full_paths(&self, options: &ArgMatches) -> bool {
Some(true) == self.display_full_paths || options.is_present("display_full_paths")
}
pub fn get_reverse(&self, options: &ArgMatches) -> bool {
Some(true) == self.reverse || options.is_present("reverse")
}
pub fn get_no_bars(&self, options: &ArgMatches) -> bool {
Some(true) == self.no_bars || options.is_present("no_bars")
}
pub fn get_iso(&self, options: &ArgMatches) -> bool {
Some(true) == self.iso || options.is_present("iso")
}
pub fn get_skip_total(&self, options: &ArgMatches) -> bool {
Some(true) == self.skip_total || options.is_present("skip_total")
}
pub fn get_min_size(&self, options: &ArgMatches, iso: bool) -> Option<usize> {
let size_from_param = options.value_of("min_size");
self._get_min_size(size_from_param, iso)
}
fn _get_min_size(&self, min_size: Option<&str>, iso: bool) -> Option<usize> {
let size_from_param = min_size.and_then(|a| convert_min_size(a, iso));
if size_from_param.is_none() {
self.min_size
.as_ref()
.and_then(|a| convert_min_size(a.as_ref(), iso))
} else {
size_from_param
}
}
}
fn convert_min_size(input: &str, iso: bool) -> Option<usize> {
let chars_as_vec: Vec<char> = input.chars().collect();
match chars_as_vec.split_last() {
Some((last, start)) => {
let mut starts: String = start.iter().collect::<String>();
for (i, u) in UNITS.iter().rev().enumerate() {
if Some(*u) == last.to_uppercase().next() {
return match starts.parse::<usize>() {
Ok(pure) => {
let num: usize = if iso { 1000 } else { 1024 };
let marker = pure * num.pow((i + 1) as u32);
Some(marker)
}
Err(_) => {
eprintln!("Ignoring invalid min-size: {}", input);
None
}
};
}
}
starts.push(*last);
starts
.parse()
.map_err(|_| {
eprintln!("Ignoring invalid min-size: {}", input);
})
.ok()
}
None => None,
}
}
fn get_config_locations(base: &Path) -> Vec<PathBuf> {
vec![
base.join(".dust.toml"),
base.join(".config").join("dust").join("config.toml"),
]
}
pub fn get_config() -> Config {
if let Some(home) = directories::BaseDirs::new() {
for path in get_config_locations(home.home_dir()) {
if path.exists() {
if let Ok(config) = Config::from_config_file(path) {
return config;
}
}
}
}
Config {
..Default::default()
}
}
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_conversion() {
assert_eq!(convert_min_size("55", false), Some(55));
assert_eq!(convert_min_size("12344321", false), Some(12344321));
assert_eq!(convert_min_size("95RUBBISH", false), None);
assert_eq!(convert_min_size("10K", false), Some(10 * 1024));
assert_eq!(convert_min_size("10M", false), Some(10 * 1024usize.pow(2)));
assert_eq!(convert_min_size("10M", true), Some(10 * 1000usize.pow(2)));
assert_eq!(convert_min_size("2G", false), Some(2 * 1024usize.pow(3)));
}
#[test]
fn test_min_size_from_config_applied_or_overridden() {
let c = Config {
min_size: Some("1K".to_owned()),
..Default::default()
};
assert_eq!(c._get_min_size(None, false), Some(1024));
assert_eq!(c._get_min_size(Some("2K"), false), Some(2048));
assert_eq!(c._get_min_size(None, true), Some(1000));
assert_eq!(c._get_min_size(Some("2K"), true), Some(2000));
}
}

226
src/dir_walker.rs Normal file
View File

@@ -0,0 +1,226 @@
use std::fs;
use crate::node::Node;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use regex::Regex;
use std::path::PathBuf;
use std::sync::atomic;
use std::sync::atomic::AtomicBool;
use std::collections::HashSet;
use crate::node::build_node;
use std::fs::DirEntry;
use crate::platform::get_metadata;
pub struct WalkData<'a> {
pub ignore_directories: HashSet<PathBuf>,
pub filter_regex: &'a [Regex],
pub invert_filter_regex: &'a [Regex],
pub allowed_filesystems: HashSet<u64>,
pub use_apparent_size: bool,
pub by_filecount: bool,
pub ignore_hidden: bool,
}
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> (Vec<Node>, bool) {
let permissions_flag = AtomicBool::new(false);
let top_level_nodes: Vec<_> = dirs
.into_iter()
.filter_map(|d| {
clean_inodes(
walk(d, &permissions_flag, &walk_data, 0)?,
&mut HashSet::new(),
walk_data.use_apparent_size,
)
})
.collect();
(top_level_nodes, permissions_flag.into_inner())
}
// Remove files which have the same inode, we don't want to double count them.
fn clean_inodes(
x: Node,
inodes: &mut HashSet<(u64, u64)>,
use_apparent_size: bool,
) -> Option<Node> {
if !use_apparent_size {
if let Some(id) = x.inode_device {
if !inodes.insert(id) {
return None;
}
}
}
// Sort Nodes so iteration order is predictable
let mut tmp: Vec<_> = x.children;
tmp.sort_by(sort_by_inode);
let new_children: Vec<_> = tmp
.into_iter()
.filter_map(|c| clean_inodes(c, inodes, use_apparent_size))
.collect();
Some(Node {
name: x.name,
size: x.size + new_children.iter().map(|c| c.size).sum::<u64>(),
children: new_children,
inode_device: x.inode_device,
depth: x.depth,
})
}
fn sort_by_inode(a: &Node, b: &Node) -> std::cmp::Ordering {
// Sorting by inode is quicker than by sorting by name/size
if let Some(x) = a.inode_device {
if let Some(y) = b.inode_device {
if x.0 != y.0 {
return x.0.cmp(&y.0);
} else if x.1 != y.1 {
return x.1.cmp(&y.1);
}
}
}
a.name.cmp(&b.name)
}
fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.');
let is_ignored_path = walk_data.ignore_directories.contains(&entry.path());
if !walk_data.allowed_filesystems.is_empty() {
let size_inode_device = get_metadata(&entry.path(), false);
if let Some((_size, Some((_id, dev)))) = size_inode_device {
if !walk_data.allowed_filesystems.contains(&dev) {
return true;
}
}
}
// Keeping `walk_data.filter_regex.is_empty()` is important for performance reasons, it stops unnecessary work
if !walk_data.filter_regex.is_empty()
&& entry.path().is_file()
&& is_filtered_out_due_to_regex(walk_data.filter_regex, &entry.path())
{
return true;
}
if !walk_data.invert_filter_regex.is_empty()
&& entry.path().is_file()
&& is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &entry.path())
{
return true;
}
(is_dot_file && walk_data.ignore_hidden) || is_ignored_path
}
fn walk(
dir: PathBuf,
permissions_flag: &AtomicBool,
walk_data: &WalkData,
depth: usize,
) -> Option<Node> {
let mut children = vec![];
if let Ok(entries) = fs::read_dir(&dir) {
children = entries
.into_iter()
.par_bridge()
.filter_map(|entry| {
if let Ok(ref entry) = entry {
// uncommenting the below line gives simpler code but
// rayon doesn't parallelize as well giving a 3X performance drop
// hence we unravel the recursion a bit
// return walk(entry.path(), permissions_flag, ignore_directories, allowed_filesystems, use_apparent_size, by_filecount, ignore_hidden);
if !ignore_file(entry, walk_data) {
if let Ok(data) = entry.file_type() {
return if data.is_dir() && !data.is_symlink() {
walk(entry.path(), permissions_flag, walk_data, depth + 1)
} else {
build_node(
entry.path(),
vec![],
walk_data.filter_regex,
walk_data.invert_filter_regex,
walk_data.use_apparent_size,
data.is_symlink(),
data.is_file(),
walk_data.by_filecount,
depth,
)
};
}
}
} else {
permissions_flag.store(true, atomic::Ordering::Relaxed);
}
None
})
.collect();
} else {
// Handle edge case where dust is called with a file instead of a directory
if !dir.exists() {
permissions_flag.store(true, atomic::Ordering::Relaxed);
}
}
build_node(
dir,
children,
walk_data.filter_regex,
walk_data.invert_filter_regex,
walk_data.use_apparent_size,
false,
false,
walk_data.by_filecount,
depth,
)
}
mod tests {
#[allow(unused_imports)]
use super::*;
#[cfg(test)]
fn create_node() -> Node {
Node {
name: PathBuf::new(),
size: 10,
children: vec![],
inode_device: Some((5, 6)),
depth: 0,
}
}
#[test]
#[allow(clippy::redundant_clone)]
fn test_should_ignore_file() {
let mut inodes = HashSet::new();
let n = create_node();
// First time we insert the node
assert_eq!(clean_inodes(n.clone(), &mut inodes, false), Some(n.clone()));
// Second time is a duplicate - we ignore it
assert_eq!(clean_inodes(n.clone(), &mut inodes, false), None);
}
#[test]
#[allow(clippy::redundant_clone)]
fn test_should_not_ignore_files_if_using_apparent_size() {
let mut inodes = HashSet::new();
let n = create_node();
// If using apparent size we include Nodes, even if duplicate inodes
assert_eq!(clean_inodes(n.clone(), &mut inodes, true), Some(n.clone()));
assert_eq!(clean_inodes(n.clone(), &mut inodes, true), Some(n.clone()));
}
}

476
src/display.rs Normal file
View File

@@ -0,0 +1,476 @@
use crate::display_node::DisplayNode;
use 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;
pub static UNITS: [char; 4] = ['T', 'G', 'M', 'K'];
static BLOCKS: [char; 5] = ['█', '▓', '▒', '░', ' '];
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,
pub iso: bool,
}
impl DisplayData {
fn get_tree_chars(&self, was_i_last: bool, has_children: bool) -> &'static str {
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) => "├──",
}
}
fn is_biggest(&self, num_siblings: usize, max_siblings: u64) -> bool {
if self.is_reversed {
num_siblings == (max_siblings - 1) as usize
} else {
num_siblings == 0
}
}
fn is_last(&self, num_siblings: usize, max_siblings: u64) -> bool {
if self.is_reversed {
num_siblings == 0
} else {
num_siblings == (max_siblings - 1) as usize
}
}
fn percent_size(&self, node: &DisplayNode) -> f32 {
let result = node.size as f32 / self.base_size as f32;
if result.is_normal() {
result
} else {
0.0
}
}
}
struct DrawData<'a> {
indent: String,
percent_bar: String,
display_data: &'a DisplayData,
}
impl DrawData<'_> {
fn get_new_indent(&self, has_children: bool, was_i_last: bool) -> String {
let chars = self.display_data.get_tree_chars(was_i_last, has_children);
self.indent.to_string() + chars
}
// TODO: can we test this?
fn generate_bar(&self, node: &DisplayNode, 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(4, max(1, level));
for c in self.percent_bar.chars() {
num_not_my_bar -= 1;
if num_not_my_bar <= 0 {
new_bar.push(BLOCKS[0]);
} else if c == BLOCKS[0] {
new_bar.push(BLOCKS[idx]);
} else {
new_bar.push(c);
}
}
new_bar
}
}
#[allow(clippy::too_many_arguments)]
pub fn draw_it(
use_full_path: bool,
is_reversed: bool,
no_colors: bool,
no_percent_bars: bool,
terminal_width: usize,
by_filecount: bool,
root_node: &DisplayNode,
iso: bool,
skip_total: bool,
) {
let biggest = match skip_total {
false => root_node,
true => root_node
.get_children_from_node(false)
.next()
.unwrap_or(root_node),
};
let num_chars_needed_on_left_most = if by_filecount {
let max_size = biggest.size;
max_size.separate_with_commas().chars().count()
} else {
find_biggest_size_str(root_node, iso)
};
assert!(
terminal_width > num_chars_needed_on_left_most + 2,
"Not enough terminal width"
);
let allowed_width = terminal_width - num_chars_needed_on_left_most - 2;
let num_indent_chars = 3;
let longest_string_length =
find_longest_dir_name(root_node, num_indent_chars, allowed_width, !use_full_path);
let max_bar_length = if no_percent_bars || longest_string_length + 7 >= allowed_width as usize {
0
} else {
allowed_width as usize - longest_string_length - 7
};
let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect();
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: biggest.size,
longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(),
iso,
};
let draw_data = DrawData {
indent: "".to_string(),
percent_bar: first_size_bar,
display_data: &display_data,
};
if !skip_total {
display_node(root_node, &draw_data, true, true);
} else {
for (count, c) in root_node
.get_children_from_node(draw_data.display_data.is_reversed)
.enumerate()
{
let is_biggest = display_data.is_biggest(count, root_node.num_siblings());
let was_i_last = display_data.is_last(count, root_node.num_siblings());
display_node(c, &draw_data, is_biggest, was_i_last);
}
}
}
fn find_biggest_size_str(node: &DisplayNode, iso: bool) -> usize {
let mut mx = human_readable_number(node.size, iso).chars().count();
for n in node.children.iter() {
mx = max(mx, find_biggest_size_str(n, iso));
}
mx
}
fn find_longest_dir_name(
node: &DisplayNode,
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 more chars, hence we increment indent by 2
node.children
.iter()
.map(|c| find_longest_dir_name(c, indent + 2, terminal, long_paths))
.fold(longest, max)
}
fn display_node(node: &DisplayNode, draw_data: &DrawData, is_biggest: bool, is_last: bool) {
// hacky way of working out how deep we are in the tree
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, &indent, &bar_text, is_biggest, draw_data.display_data);
if !draw_data.display_data.is_reversed {
println!("{}", to_print)
}
let dd = DrawData {
indent: clean_indentation_string(&indent),
percent_bar: bar_text,
display_data: draw_data.display_data,
};
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);
}
if draw_data.display_data.is_reversed {
println!("{}", to_print)
}
}
fn clean_indentation_string(s: &str) -> String {
let mut is: String = s.into();
// For reversed:
is = is.replace("┌─┴", " ");
is = is.replace("┌──", " ");
is = is.replace("├─┴", "");
is = is.replace("─┴", " ");
// For normal
is = is.replace("└─┬", " ");
is = is.replace("└──", " ");
is = is.replace("├─┬", "");
is = is.replace("─┬", " ");
// For both
is = is.replace("├──", "");
is
}
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 {
match dir_name.parent() {
Some(prefix) => match dir_name.strip_prefix(prefix) {
Ok(base) => base,
Err(_) => dir_name,
},
None => dir_name,
}
} else {
dir_name
}
};
encode_u8(printable_name.display().to_string().as_bytes())
}
fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &DisplayData) -> String {
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);
assert!(
display_data.longest_string_length >= width,
"Terminal width not wide enough to draw directory tree"
);
// Add spaces after the filename so we can draw the % used bar chart.
let name_and_padding = name
+ " "
.repeat(display_data.longest_string_length - width)
.as_str();
name_and_padding
}
fn maybe_trim_filename(name_in: String, indent: &str, display_data: &DisplayData) -> String {
let indent_length = UnicodeWidthStr::width(indent);
assert!(
display_data.longest_string_length >= indent_length + 2,
"Terminal width not wide enough to draw directory tree"
);
let max_size = display_data.longest_string_length - indent_length;
if UnicodeWidthStr::width(&*name_in) > max_size {
let name = name_in.chars().take(max_size - 2).collect::<String>();
name + ".."
} else {
name_in
}
}
pub fn format_string(
node: &DisplayNode,
indent: &str,
percent_bar: &str,
is_biggest: bool,
display_data: &DisplayData,
) -> String {
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)
}
fn get_name_percent(
node: &DisplayNode,
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 {
let n = get_printable_name(&node.name, display_data.short_paths);
let name = maybe_trim_filename(n, indent, display_data);
("".into(), name)
}
}
fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayData) -> String {
let output = if display_data.by_filecount {
node.size.separate_with_commas()
} else {
human_readable_number(node.size, display_data.iso)
};
let spaces_to_add = display_data.num_chars_needed_on_left_most - output.chars().count();
let output = " ".repeat(spaces_to_add) + output.as_str();
if is_biggest && display_data.colors_on {
format!("{}", Red.paint(output))
} else {
output
}
}
fn get_pretty_name(
node: &DisplayNode,
name_and_padding: String,
display_data: &DisplayData,
) -> String {
if display_data.colors_on {
let meta_result = fs::metadata(&node.name);
let directory_color = display_data
.ls_colors
.style_for_path_with_metadata(&node.name, 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, iso: bool) -> String {
for (i, u) in UNITS.iter().enumerate() {
let num: u64 = if iso { 1000 } else { 1024 };
let marker = num.pow((UNITS.len() - i) as u32);
if size >= marker {
if size / marker < 10 {
return format!("{:.1}{}", (size as f32 / marker as f32), u);
} else {
return format!("{}{}", (size / marker), u);
}
}
}
format!("{}B", size)
}
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(),
iso: false,
}
}
#[test]
fn test_format_str() {
let n = DisplayNode {
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(20),
);
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 = DisplayNode {
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_.."
);
}
#[test]
fn test_human_readable_number() {
assert_eq!(human_readable_number(1, false), "1B");
assert_eq!(human_readable_number(956, false), "956B");
assert_eq!(human_readable_number(1004, false), "1004B");
assert_eq!(human_readable_number(1024, false), "1.0K");
assert_eq!(human_readable_number(1536, false), "1.5K");
assert_eq!(human_readable_number(1024 * 512, false), "512K");
assert_eq!(human_readable_number(1024 * 1024, false), "1.0M");
assert_eq!(
human_readable_number(1024 * 1024 * 1024 - 1, false),
"1023M"
);
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20, false), "20G");
assert_eq!(
human_readable_number(1024 * 1024 * 1024 * 1024, false),
"1.0T"
);
}
}

25
src/display_node.rs Normal file
View File

@@ -0,0 +1,25 @@
use std::path::PathBuf;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct DisplayNode {
// Note: the order of fields in important here, for PartialEq and PartialOrd
pub size: u64,
pub name: PathBuf, //todo: consider moving to a string?
pub children: Vec<DisplayNode>,
}
impl DisplayNode {
pub fn num_siblings(&self) -> u64 {
self.children.len() as u64
}
pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = &DisplayNode> {
// we box to avoid the clippy lint warning
let out: Box<dyn Iterator<Item = &DisplayNode>> = if is_reversed {
Box::new(self.children.iter().rev())
} else {
Box::new(self.children.iter())
};
out
}
}

87
src/filter.rs Normal file
View File

@@ -0,0 +1,87 @@
use crate::display_node::DisplayNode;
use crate::node::Node;
use std::collections::BinaryHeap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
pub fn get_biggest(
top_level_nodes: Vec<Node>,
min_size: Option<usize>,
n: usize,
depth: usize,
using_a_filter: bool,
) -> Option<DisplayNode> {
if top_level_nodes.is_empty() {
// perhaps change this, bring back Error object?
return None;
}
let mut heap = BinaryHeap::new();
let number_top_level_nodes = top_level_nodes.len();
let root = get_new_root(top_level_nodes);
let mut allowed_nodes = HashSet::new();
allowed_nodes.insert(root.name.as_path());
heap = add_children(using_a_filter, min_size, &root, depth, heap);
for _ in number_top_level_nodes..n {
let line = heap.pop();
match line {
Some(line) => {
allowed_nodes.insert(line.name.as_path());
heap = add_children(using_a_filter, min_size, line, depth, heap);
}
None => break,
}
}
recursive_rebuilder(&allowed_nodes, &root)
}
fn add_children<'a>(
using_a_filter: bool,
min_size: Option<usize>,
file_or_folder: &'a Node,
depth: usize,
mut heap: BinaryHeap<&'a Node>,
) -> BinaryHeap<&'a Node> {
if depth > file_or_folder.depth {
heap.extend(file_or_folder.children.iter().filter(|c| match min_size {
Some(ms) => c.size > ms as u64,
None => !using_a_filter || c.name.is_file() || c.size > 0,
}))
}
heap
}
fn get_new_root(top_level_nodes: Vec<Node>) -> Node {
if top_level_nodes.len() != 1 {
let size = top_level_nodes.iter().map(|node| node.size).sum();
Node {
name: PathBuf::from("(total)"),
size,
children: top_level_nodes,
inode_device: None,
depth: 0,
}
} else {
top_level_nodes.into_iter().next().unwrap()
}
}
fn recursive_rebuilder(allowed_nodes: &HashSet<&Path>, current: &Node) -> Option<DisplayNode> {
let mut new_children: Vec<_> = current
.children
.iter()
.filter(|c| allowed_nodes.contains(c.name.as_path()))
.filter_map(|c| recursive_rebuilder(allowed_nodes, c))
.collect();
new_children.sort_by(|lhs, rhs| lhs.cmp(rhs).reverse());
Some(DisplayNode {
name: current.name.clone(),
size: current.size,
children: new_children,
})
}

75
src/filter_type.rs Normal file
View File

@@ -0,0 +1,75 @@
use crate::display_node::DisplayNode;
use crate::node::Node;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::PathBuf;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct ExtensionNode<'a> {
size: u64,
extension: Option<&'a OsStr>,
}
pub fn get_all_file_types(top_level_nodes: &[Node], n: usize) -> Option<DisplayNode> {
let ext_nodes = {
let mut extension_cumulative_sizes = HashMap::new();
build_by_all_file_types(top_level_nodes, &mut extension_cumulative_sizes);
let mut extension_cumulative_sizes: Vec<ExtensionNode<'_>> = extension_cumulative_sizes
.iter()
.map(|(&extension, &size)| ExtensionNode { extension, size })
.collect();
extension_cumulative_sizes.sort_by(|lhs, rhs| lhs.cmp(rhs).reverse());
extension_cumulative_sizes
};
let mut ext_nodes_iter = ext_nodes.iter();
// First, collect the first N - 1 nodes...
let mut displayed: Vec<DisplayNode> = ext_nodes_iter
.by_ref()
.take(if n > 1 { n - 1 } else { 1 })
.map(|node| DisplayNode {
name: PathBuf::from(
node.extension
.map(|ext| format!(".{}", ext.to_string_lossy()))
.unwrap_or_else(|| "(no extension)".to_owned()),
),
size: node.size,
children: vec![],
})
.collect();
// ...then, aggregate the remaining nodes (if any) into a single "(others)" node
if ext_nodes_iter.len() > 0 {
displayed.push(DisplayNode {
name: PathBuf::from("(others)"),
size: ext_nodes_iter.map(|node| node.size).sum(),
children: vec![],
});
}
let result = DisplayNode {
name: PathBuf::from("(total)"),
size: displayed.iter().map(|node| node.size).sum(),
children: displayed,
};
Some(result)
}
fn build_by_all_file_types<'a>(
top_level_nodes: &'a [Node],
counter: &mut HashMap<Option<&'a OsStr>, u64>,
) {
for node in top_level_nodes {
if node.name.is_file() {
let ext = node.name.extension();
let cumulative_size = counter.entry(ext).or_default();
*cumulative_size += node.size;
}
build_by_all_file_types(&node.children, counter)
}
}

View File

@@ -1,364 +1,201 @@
// test:
// recursive dirs that link to each other.
// Pass in bad dir name
// num to search for is less than num available
// admin files.
//
extern crate ansi_term;
#[macro_use]
extern crate clap;
mod cli;
mod config;
mod dir_walker;
mod display;
mod display_node;
mod filter;
mod filter_type;
mod node;
mod platform;
mod utils;
use crate::cli::build_cli;
use std::collections::HashSet;
use std::panic;
use std::process;
use ansi_term::Colour::Fixed;
use clap::{App, AppSettings, Arg};
use self::display::draw_it;
use clap::Values;
use config::get_config;
use dir_walker::{walk_it, WalkData};
use filter::get_biggest;
use filter_type::get_all_file_types;
use rayon::ThreadPoolBuildError;
use regex::Regex;
use std::cmp::max;
use std::path::PathBuf;
use terminal_size::{terminal_size, Height, Width};
use utils::get_filesystem_devices;
use utils::simplify_dir_names;
use std::cmp;
use std::cmp::Ordering;
use std::fs;
use std::fs::ReadDir;
use std::io;
static DEFAULT_NUMBER_OF_LINES: usize = 30;
static DEFAULT_TERMINAL_WIDTH: usize = 80;
#[derive(Clone, Debug)]
struct Node {
dir: Dir,
children: Vec<Node>,
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
if self.dir.size > other.dir.size {
Ordering::Less
} else if self.dir.size < other.dir.size {
Ordering::Greater
fn init_color(no_color: bool) -> bool {
#[cfg(windows)]
{
// If no color is already set do not print a warning message
if no_color {
true
} else {
let my_slashes = self.dir.name.matches("/").count();
let other_slashes = other.dir.name.matches("/").count();
if my_slashes > other_slashes {
Ordering::Greater
} else if my_slashes < other_slashes {
Ordering::Less
} else {
if self.dir.name < other.dir.name {
Ordering::Less
} else if self.dir.name > other.dir.name {
Ordering::Greater
} else {
Ordering::Equal
// 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
}
}
}
}
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
#[cfg(not(windows))]
{
no_color
}
}
impl PartialEq for Node {
fn eq(&self, other: &Self) -> bool {
(&self.dir.name, self.dir.size) == (&other.dir.name, other.dir.size)
}
}
impl Eq for Node {}
#[derive(Clone, Debug)]
struct Dir {
name: String,
size: u64,
fn get_height_of_terminal() -> usize {
// Simplify once https://github.com/eminence/terminal-size/pull/41 is
// merged
terminal_size()
// Windows CI runners detect a terminal height of 0
.map(|(_, Height(h))| max(h as usize, DEFAULT_NUMBER_OF_LINES))
.unwrap_or(DEFAULT_NUMBER_OF_LINES)
- 10
}
static DEFAULT_NUMBER_OF_LINES: &'static str = &"15";
fn get_width_of_terminal() -> usize {
// Simplify once https://github.com/eminence/terminal-size/pull/41 is
// merged
terminal_size()
.map(|(Width(w), _)| match cfg!(windows) {
// Windows CI runners detect a very low terminal width
true => max(w as usize, DEFAULT_TERMINAL_WIDTH),
false => w as usize,
})
.unwrap_or(DEFAULT_TERMINAL_WIDTH)
}
fn get_regex_value(maybe_value: Option<Values>) -> Vec<Regex> {
maybe_value
.unwrap_or_default()
.map(|reg| {
Regex::new(reg).unwrap_or_else(|err| {
eprintln!("Ignoring bad value for regex {:?}", err);
process::exit(1)
})
})
.collect()
}
fn main() {
let options = App::new("Trailing args example")
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name("number_of_lines")
.short("n")
.help("Number of lines of output to show")
.takes_value(true)
.default_value(DEFAULT_NUMBER_OF_LINES),
)
.arg(Arg::with_name("inputs").multiple(true))
.get_matches();
let options = build_cli().get_matches();
let config = get_config();
let filenames = {
match options.values_of("inputs") {
None => vec!["."],
Some(r) => r.collect(),
}
let target_dirs = options
.values_of("inputs")
.expect("Should be a default value here")
.collect();
let summarize_file_types = options.is_present("types");
let filter_regexs = get_regex_value(options.values_of("filter"));
let invert_filter_regexs = get_regex_value(options.values_of("invert_filter"));
let terminal_width = options
.value_of_t("width")
.unwrap_or_else(|_| get_width_of_terminal());
let depth = options.value_of_t("depth").unwrap_or(usize::MAX);
// If depth is set, then we set the default number_of_lines to be max
// instead of screen height
let default_height = if depth != usize::MAX {
usize::MAX
} else {
get_height_of_terminal()
};
let number_of_lines = value_t!(options.value_of("number_of_lines"), usize).unwrap();
let (permissions, results) = get_dir_tree(filenames);
let slice_it = find_big_ones(&results, number_of_lines);
display(permissions, slice_it);
}
let number_of_lines = options
.value_of("number_of_lines")
.and_then(|v| {
v.parse()
.map_err(|_| eprintln!("Ignoring bad value for number_of_lines"))
.ok()
})
.unwrap_or(default_height);
fn get_dir_tree(filenames: Vec<&str>) -> (bool, Vec<Node>) {
let mut permissions = true;
let mut results = vec![];
for b in filenames {
let mut new_name = String::from(b);
while new_name.chars().last() == Some('/') && new_name.len() != 1 {
new_name.pop();
}
let (hp, data) = examine_dir_str(new_name);
permissions = permissions && hp;
results.push(data);
let no_colors = init_color(config.get_no_colors(&options));
let ignore_directories = options
.values_of("ignore_directory")
.unwrap_or_default()
.map(PathBuf::from);
let by_filecount = options.is_present("by_filecount");
let limit_filesystem = options.is_present("limit_filesystem");
let simplified_dirs = simplify_dir_names(target_dirs);
let allowed_filesystems = limit_filesystem
.then(|| get_filesystem_devices(simplified_dirs.iter()))
.unwrap_or_default();
let ignored_full_path: HashSet<PathBuf> = ignore_directories
.flat_map(|x| simplified_dirs.iter().map(move |d| d.join(&x)))
.collect();
let walk_data = WalkData {
ignore_directories: ignored_full_path,
filter_regex: &filter_regexs,
invert_filter_regex: &invert_filter_regexs,
allowed_filesystems,
use_apparent_size: config.get_apparent_size(&options),
by_filecount,
ignore_hidden: config.get_ignore_hidden(&options),
};
let pool = panic::catch_unwind(init_rayon);
if pool.is_err() {
eprintln!("Warning: Could not configure threads {:?}", pool.err());
}
(permissions, results)
}
fn examine_dir_str(loc: String) -> (bool, Node) {
let mut inodes: HashSet<u64> = HashSet::new();
let (hp, result) = examine_dir(fs::read_dir(&loc), &mut inodes);
let iso = config.get_iso(&options);
let (top_level_nodes, has_errors) = walk_it(simplified_dirs, walk_data);
// This needs to be folded into the below recursive call somehow
let new_size = result.iter().fold(0, |a, b| a + b.dir.size);
(
hp,
Node {
dir: Dir {
name: loc,
size: new_size,
},
children: result,
},
)
}
let tree = match summarize_file_types {
true => get_all_file_types(&top_level_nodes, number_of_lines),
false => get_biggest(
top_level_nodes,
config.get_min_size(&options, iso),
number_of_lines,
depth,
options.values_of("filter").is_some() || options.value_of("invert_filter").is_some(),
),
};
#[cfg(target_os = "linux")]
fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> {
use std::os::linux::fs::MetadataExt;
match d.metadata().ok() {
Some(md) => Some((md.len(), md.st_ino())),
None => None,
}
}
#[cfg(target_os = "unix")]
fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> {
use std::os::unix::fs::MetadataExt;
match d.metadata().ok() {
Some(md) => Some((md.len(), md.ino())),
None => None,
}
}
#[cfg(target_os = "macos")]
fn get_metadata_blocks_and_inode(d: &std::fs::DirEntry) -> Option<(u64, u64)> {
use std::os::macos::fs::MetadataExt;
match d.metadata().ok() {
Some(md) => Some((md.len(), md.st_ino())),
None => None,
}
}
#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))]
fn get_metadata_blocks_and_inode(_d: &std::fs::DirEntry) -> Option<(u64, u64)> {
match d.metadata().ok() {
Some(md) => Some((md.len(), 0)), //move to option not 0
None => None,
}
}
fn examine_dir(a_dir: io::Result<ReadDir>, inodes: &mut HashSet<u64>) -> (bool, Vec<Node>) {
let mut result = vec![];
let mut have_permission = true;
if a_dir.is_ok() {
let paths = a_dir.unwrap();
for dd in paths {
match dd {
Ok(d) => {
let file_type = d.file_type().ok();
let maybe_size_and_inode = get_metadata_blocks_and_inode(&d);
match (file_type, maybe_size_and_inode) {
(Some(file_type), Some((size, inode))) => {
let s = d.path().to_string_lossy().to_string();
if inodes.contains(&inode) {
continue;
}
inodes.insert(inode);
if d.path().is_dir() && !file_type.is_symlink() {
let (hp, recursive) = examine_dir(fs::read_dir(d.path()), inodes);
have_permission = have_permission && hp;
let new_size = recursive.iter().fold(size, |a, b| a + b.dir.size);
result.push(Node {
dir: Dir {
name: s,
size: new_size,
},
children: recursive,
})
} else {
result.push(Node {
dir: Dir {
name: s,
size: size,
},
children: vec![],
})
}
}
(_, None) => have_permission = false,
(_, _) => (),
}
}
Err(_) => (),
}
}
} else {
have_permission = false;
}
(have_permission, result)
}
// We start with a list of root directories - these must be the biggest folders
// We then repeadedly merge in the children of the biggest directory - Each iteration
// the next biggest directory's children are merged in.
fn find_big_ones<'a>(l: &'a Vec<Node>, max_to_show: usize) -> Vec<&Node> {
let mut new_l: Vec<&Node> = l.iter().map(|a| a).collect();
new_l.sort();
for processed_pointer in 0..max_to_show {
if new_l.len() == processed_pointer {
break;
}
// Must be a list of pointers into new_l otherwise b_list will go out of scope
// when it is deallocated
let mut b_list: Vec<&Node> = new_l[processed_pointer]
.children
.iter()
.map(|a| a)
.collect();
new_l.extend(b_list);
new_l.sort();
/*println!(
"{:?} -------------------",
new_l
.iter()
.map(|a| a.dir.size.to_string() + ": " + &a.dir.name)
.collect::<Vec<String>>()
);*/
}
if new_l.len() > max_to_show {
new_l[0..max_to_show + 1].to_vec()
} else {
new_l
}
}
fn display(permissions: bool, to_display: Vec<&Node>) -> () {
if !permissions {
if has_errors {
eprintln!("Did not have permissions for all directories");
}
display_node(to_display[0], &to_display, true, 1, "")
}
fn display_node<S: Into<String>>(
node_to_print: &Node,
to_display: &Vec<&Node>,
is_first: bool,
depth: u8,
indentation_str: S,
) {
let mut is = indentation_str.into();
print_this_node(node_to_print, is_first, depth, is.as_ref());
is = is.replace("└──", " ");
is = is.replace("├──", "");
let printable_node_slashes = node_to_print.dir.name.matches("/").count();
let mut num_sibblings = to_display.iter().fold(0, |a, b| {
if node_to_print.children.contains(b)
&& b.dir.name.matches("/").count() == printable_node_slashes + 1
{
a + 1
} else {
a
}
});
let mut is_biggest = true;
for node in to_display {
if node_to_print.children.contains(node) {
if node.dir.name.matches("/").count() == printable_node_slashes + 1 {
num_sibblings -= 1;
let tree_chars = {
if num_sibblings == 0 {
"└──"
} else {
"├──"
}
};
display_node(
&node,
to_display,
is_biggest,
depth + 1,
is.to_string() + tree_chars,
);
is_biggest = false;
}
}
if let Some(root_node) = tree {
draw_it(
config.get_full_paths(&options),
!config.get_reverse(&options),
no_colors,
config.get_no_bars(&options),
terminal_width,
by_filecount,
&root_node,
iso,
config.get_skip_total(&options),
)
}
}
fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentation_str: &str) {
let padded_size = format!("{:>5}", human_readable_number(node_to_print.dir.size),);
println!(
"{} {} {}",
if is_biggest {
Fixed(196).paint(padded_size)
} else {
Fixed(7).paint(padded_size)
},
indentation_str,
Fixed(7)
.on(Fixed(cmp::min(8, (depth) as u8) + 231))
.paint(node_to_print.dir.name.to_string())
);
}
fn human_readable_number(size: u64) -> (String) {
let units = vec!["T", "G", "M", "K"]; //make static
//return format!("{}B", size);
for (i, u) in units.iter().enumerate() {
let marker = 1024u64.pow((units.len() - i) as u32);
if size >= marker {
if size / marker < 10 {
return format!("{:.1}{}", (size as f32 / marker as f32), u);
} else {
return format!("{}{}", (size / marker), u);
}
}
}
return format!("{}B", size);
}
mod tests {
use super::*;
#[test]
fn test_human_readable_number() {
assert_eq!(human_readable_number(1), "1B");
assert_eq!(human_readable_number(956), "956B");
assert_eq!(human_readable_number(1004), "1004B");
assert_eq!(human_readable_number(1024), "1.0K");
assert_eq!(human_readable_number(1536), "1.5K");
assert_eq!(human_readable_number(1024 * 512), "512K");
assert_eq!(human_readable_number(1024 * 1024), "1.0M");
assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M");
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G");
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T");
}
fn init_rayon() -> Result<(), ThreadPoolBuildError> {
// Larger stack size to handle cases with lots of nested directories
rayon::ThreadPoolBuilder::new()
.stack_size(usize::pow(1024, 3))
.build_global()
}

78
src/node.rs Normal file
View File

@@ -0,0 +1,78 @@
use crate::platform::get_metadata;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;
use regex::Regex;
use std::cmp::Ordering;
use std::path::PathBuf;
#[derive(Debug, Eq, Clone)]
pub struct Node {
pub name: PathBuf,
pub size: u64,
pub children: Vec<Node>,
pub inode_device: Option<(u64, u64)>,
pub depth: usize,
}
#[allow(clippy::too_many_arguments)]
pub fn build_node(
dir: PathBuf,
children: Vec<Node>,
filter_regex: &[Regex],
invert_filter_regex: &[Regex],
use_apparent_size: bool,
is_symlink: bool,
is_file: bool,
by_filecount: bool,
depth: usize,
) -> Option<Node> {
get_metadata(&dir, use_apparent_size).map(|data| {
let inode_device = if is_symlink && !use_apparent_size {
None
} else {
data.1
};
let size = if is_filtered_out_due_to_regex(filter_regex, &dir)
|| is_filtered_out_due_to_invert_regex(invert_filter_regex, &dir)
|| (is_symlink && !use_apparent_size)
|| by_filecount && !is_file
{
0
} else if by_filecount {
1
} else {
data.0
};
Node {
name: dir,
size,
children,
inode_device,
depth,
}
})
}
impl PartialEq for Node {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.size == other.size && self.children == other.children
}
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
self.size
.cmp(&other.size)
.then_with(|| self.name.cmp(&other.name))
.then_with(|| self.children.cmp(&other.children))
}
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

128
src/platform.rs Normal file
View File

@@ -0,0 +1,128 @@
#[allow(unused_imports)]
use std::fs;
use std::path::Path;
#[cfg(target_family = "unix")]
fn get_block_size() -> u64 {
// All os specific implementations of MetadataExt seem to define a block as 512 bytes
// https://doc.rust-lang.org/std/os/linux/fs/trait.MetadataExt.html#tymethod.st_blocks
512
}
#[cfg(target_family = "unix")]
pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::unix::fs::MetadataExt;
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: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
// On windows opening the file to get size, file ID and volume can be very
// expensive because 1) it causes a few system calls, and more importantly 2) it can cause
// windows defender to scan the file.
// 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 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;
// 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:
// 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: &Path) -> Option<(u64, Option<(u64, u64)>)> {
use winapi_util::file::information;
let h = handle_from_path_limited(d).ok()?;
let info = information(&h).ok()?;
Some((
info.file_size(),
Some((info.file_index(), info.volume_serial_number())),
))
}
use std::os::windows::fs::MetadataExt;
match d.metadata() {
Ok(ref md) => {
const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20;
const FILE_ATTRIBUTE_READONLY: u32 = 0x01;
const FILE_ATTRIBUTE_HIDDEN: u32 = 0x02;
const FILE_ATTRIBUTE_SYSTEM: u32 = 0x04;
const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
let attr_filtered = md.file_attributes()
& !(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
if (attr_filtered & FILE_ATTRIBUTE_ARCHIVE) != 0
|| (attr_filtered & FILE_ATTRIBUTE_DIRECTORY) != 0
|| md.file_attributes() == FILE_ATTRIBUTE_NORMAL
{
Some((md.len(), None))
} else {
get_metadata_expensive(d)
}
}
_ => get_metadata_expensive(d),
}
}

158
src/utils.rs Normal file
View File

@@ -0,0 +1,158 @@
use platform::get_metadata;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use crate::platform;
use regex::Regex;
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
let mut top_level_names: HashSet<PathBuf> = HashSet::with_capacity(filenames.len());
for t in filenames {
let top_level_name = normalize_path(t);
let mut can_add = true;
let mut to_remove: Vec<PathBuf> = Vec::new();
for tt in top_level_names.iter() {
if is_a_parent_of(&top_level_name, tt) {
to_remove.push(tt.to_path_buf());
} else if is_a_parent_of(tt, &top_level_name) {
can_add = false;
}
}
for r in to_remove {
top_level_names.remove(&r);
}
if can_add {
top_level_names.insert(top_level_name);
}
}
top_level_names
}
pub fn get_filesystem_devices<'a, P: IntoIterator<Item = &'a PathBuf>>(paths: P) -> HashSet<u64> {
// Gets the device ids for the filesystems which are used by the argument paths
paths
.into_iter()
.filter_map(|p| match get_metadata(p, false) {
Some((_size, Some((_id, dev)))) => Some(dev),
_ => None,
})
.collect()
}
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
// normalize path ...
// 1. removing repeated separators
// 2. removing interior '.' ("current directory") path segments
// 3. removing trailing extra separators and '.' ("current directory") path segments
// * `Path.components()` does all the above work; ref: <https://doc.rust-lang.org/std/path/struct.Path.html#method.components>
// 4. changing to os preferred separator (automatically done by recollecting components back into a PathBuf)
path.as_ref().components().collect()
}
pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool {
if filter_regex.is_empty() {
false
} else {
filter_regex
.iter()
.all(|f| !f.is_match(&dir.as_os_str().to_string_lossy()))
}
}
pub fn is_filtered_out_due_to_invert_regex(filter_regex: &[Regex], dir: &Path) -> bool {
filter_regex
.iter()
.any(|f| f.is_match(&dir.as_os_str().to_string_lossy()))
}
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
let parent = parent.as_ref();
let child = child.as_ref();
child.starts_with(parent) && !parent.starts_with(child)
}
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_simplify_dir() {
let mut correct = HashSet::new();
correct.insert(PathBuf::from("a"));
assert_eq!(simplify_dir_names(vec!["a"]), correct);
}
#[test]
fn test_simplify_dir_rm_subdir() {
let mut correct = HashSet::new();
correct.insert(["a", "b"].iter().collect::<PathBuf>());
assert_eq!(simplify_dir_names(vec!["a/b/c", "a/b", "a/b/d/f"]), correct);
assert_eq!(simplify_dir_names(vec!["a/b", "a/b/c", "a/b/d/f"]), correct);
}
#[test]
fn test_simplify_dir_duplicates() {
let mut correct = HashSet::new();
correct.insert(["a", "b"].iter().collect::<PathBuf>());
correct.insert(PathBuf::from("c"));
assert_eq!(
simplify_dir_names(vec![
"a/b",
"a/b//",
"a/././b///",
"c",
"c/",
"c/.",
"c/././",
"c/././."
]),
correct
);
}
#[test]
fn test_simplify_dir_rm_subdir_and_not_substrings() {
let mut correct = HashSet::new();
correct.insert(PathBuf::from("b"));
correct.insert(["c", "a", "b"].iter().collect::<PathBuf>());
correct.insert(["a", "b"].iter().collect::<PathBuf>());
assert_eq!(simplify_dir_names(vec!["a/b", "c/a/b/", "b"]), correct);
}
#[test]
fn test_simplify_dir_dots() {
let mut correct = HashSet::new();
correct.insert(PathBuf::from("src"));
assert_eq!(simplify_dir_names(vec!["src/."]), correct);
}
#[test]
fn test_simplify_dir_substring_names() {
let mut correct = HashSet::new();
correct.insert(PathBuf::from("src"));
correct.insert(PathBuf::from("src_v2"));
assert_eq!(simplify_dir_names(vec!["src/", "src_v2"]), correct);
}
#[test]
fn test_is_a_parent_of() {
assert!(is_a_parent_of("/usr", "/usr/andy"));
assert!(is_a_parent_of("/usr", "/usr/andy/i/am/descendant"));
assert!(!is_a_parent_of("/usr", "/usr/."));
assert!(!is_a_parent_of("/usr", "/usr/"));
assert!(!is_a_parent_of("/usr", "/usr"));
assert!(!is_a_parent_of("/usr/", "/usr"));
assert!(!is_a_parent_of("/usr/andy", "/usr"));
assert!(!is_a_parent_of("/usr/andy", "/usr/sibling"));
assert!(!is_a_parent_of("/usr/folder", "/usr/folder_not_a_child"));
}
#[test]
fn test_is_a_parent_of_root() {
assert!(is_a_parent_of("/", "/usr/andy"));
assert!(is_a_parent_of("/", "/usr"));
assert!(!is_a_parent_of("/", "/"));
}
}

View File

View File

@@ -0,0 +1 @@
hello

View File

@@ -0,0 +1 @@
hello

View File

@@ -0,0 +1 @@
hello

View File

@@ -0,0 +1 @@
hello

View File

@@ -0,0 +1 @@
hi

View File

208
tests/test_exact_output.rs Normal file
View File

@@ -0,0 +1,208 @@
use assert_cmd::Command;
use std::ffi::OsStr;
use std::str;
use std::sync::Once;
static INIT: Once = Once::new();
/**
* 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 in case 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>();
let _ = Command::new("rm")
.arg("-rf")
.arg("/tmp/".to_owned() + &*last_part_of_dir)
.ok();
let _ = Command::new("cp")
.arg("-r")
.arg(dir)
.arg("/tmp/")
.ok()
.map_err(|err| eprintln!("Error copying directory for test setup\n{:?}", err));
}
fn initialize() {
INIT.call_once(|| {
copy_test_data("tests/test_dir");
copy_test_data("tests/test_dir2");
copy_test_data("tests/test_dir_unicode");
});
}
fn exact_output_test<T: AsRef<OsStr>>(valid_outputs: Vec<String>, command_args: Vec<T>) {
initialize();
let mut a = &mut Command::cargo_bin("dust").unwrap();
for p in command_args {
a = a.arg(p);
}
let output = str::from_utf8(&a.unwrap().stdout).unwrap().to_owned();
assert!(valid_outputs.iter().any(|i| output.contains(i)));
}
// "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_basic() {
// -c is no color mode - This makes testing much simpler
exact_output_test(main_output(), vec!["-c", "/tmp/test_dir/"])
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_multi_arg() {
let command_args = vec![
"-c",
"/tmp/test_dir/many/",
"/tmp/test_dir",
"/tmp/test_dir",
];
exact_output_test(main_output(), command_args);
}
fn main_output() -> Vec<String> {
// Some linux currently thought to be Manjaro, Arch
// Although probably depends on how drive is formatted
let mac_and_some_linux = r#"
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│█████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ many │█████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ test_dir │█████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string();
let ubuntu = r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33%
8.0K ┌─┴ many │ █████████████████████████████████ │ 67%
12K ┌─┴ test_dir │█████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string();
vec![mac_and_some_linux, ubuntu]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_long_paths() {
let command_args = vec!["-c", "-p", "/tmp/test_dir/"];
exact_output_test(main_output_long_paths(), command_args);
}
fn main_output_long_paths() -> Vec<String> {
let mac_and_some_linux = r#"
0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── /tmp/test_dir/many/hello_file│██████████████████████████████ │ 100%
4.0K ┌─┴ /tmp/test_dir/many │██████████████████████████████ │ 100%
4.0K ┌─┴ /tmp/test_dir │██████████████████████████████ │ 100%
"#
.trim()
.to_string();
let ubuntu = r#"
0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░███████████ │ 33%
8.0K ┌─┴ /tmp/test_dir/many │ █████████████████████ │ 67%
12K ┌─┴ /tmp/test_dir │██████████████████████████████ │ 100%
"#
.trim()
.to_string();
vec![mac_and_some_linux, ubuntu]
}
// Check against directories and files whose names are substrings of each other
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_substring_of_names_and_long_names() {
let command_args = vec!["-c", "/tmp/test_dir2"];
exact_output_test(no_substring_of_names_output(), command_args);
}
fn no_substring_of_names_output() -> Vec<String> {
let ubuntu = "
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes..
4.0K ├── dir_name_clash
4.0K │ ┌── hello
8.0K ├─┴ dir
4.0K │ ┌── hello
8.0K ├─┴ dir_substring
24K ┌─┴ test_dir2
"
.trim()
.into();
let mac_and_some_linux = "
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes..
4.0K │ ┌── hello
4.0K ├─┴ dir
4.0K ├── dir_name_clash
4.0K │ ┌── hello
4.0K ├─┴ dir_substring
12K ┌─┴ test_dir2
"
.trim()
.into();
vec![mac_and_some_linux, ubuntu]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_unicode_directories() {
let command_args = vec!["-c", "/tmp/test_dir_unicode"];
exact_output_test(unicode_dir(), command_args);
}
fn unicode_dir() -> Vec<String> {
// The way unicode & asian characters are rendered on the terminal should make this line up
let ubuntu = "
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
0B ├── 👩.unicode │ █ │ 0%
4.0K ┌─┴ test_dir_unicode │███████████████████████████████████ │ 100%
"
.trim()
.into();
let mac_and_some_linux = "
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
0B ├── 👩.unicode │ █ │ 0%
0B ┌─┴ test_dir_unicode │ █ │ 0%
"
.trim()
.into();
vec![mac_and_some_linux, ubuntu]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_apparent_size() {
let command_args = vec!["-c", "-s", "-b", "/tmp/test_dir"];
exact_output_test(apparent_size_output(), command_args);
}
fn apparent_size_output() -> Vec<String> {
// The apparent directory sizes are too unpredictable and system dependent to try and match
let files = r#"
0B ┌── a_file
6B ├── hello_file
"#
.trim()
.to_string();
vec![files]
}

195
tests/test_flags.rs Normal file
View File

@@ -0,0 +1,195 @@
use assert_cmd::Command;
use std::ffi::OsStr;
use std::str;
/**
* This file contains tests that test a substring of the output using '.contains'
*
* These tests should be the same cross platform
*/
fn build_command<T: AsRef<OsStr>>(command_args: Vec<T>) -> String {
let mut cmd = &mut Command::cargo_bin("dust").unwrap();
for p in command_args {
cmd = cmd.arg(p);
}
let finished = &cmd.unwrap();
let stderr = str::from_utf8(&finished.stderr).unwrap();
assert_eq!(stderr, "");
str::from_utf8(&finished.stdout).unwrap().into()
}
// We can at least test the file names are there
#[test]
pub fn test_basic_output() {
let output = build_command(vec!["tests/test_dir/"]);
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 output = build_command(vec!["-b", "tests/test_dir/"]);
// If bars are not being shown we don't need to pad the output with spaces
assert!(output.contains("many"));
assert!(!output.contains("many "));
}
#[test]
pub fn test_reverse_flag() {
let output = build_command(vec!["-r", "-c", "tests/test_dir/"]);
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 output = build_command(vec!["-d", "1", "tests/test_dir/"]);
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 output = build_command(vec!["-d", "1", "-f", "-c", "tests/test_dir2/"]);
assert!(output.contains("4 ┌─┴ test_dir2"));
}
// Check against directories and files whose names are substrings of each other
#[test]
pub fn test_ignore_dir() {
let output = build_command(vec!["-c", "-X", "dir_substring", "tests/test_dir2/"]);
assert!(!output.contains("dir_substring"));
}
#[test]
pub fn test_with_bad_param() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let result = cmd.arg("bad_place").unwrap();
let stderr = str::from_utf8(&result.stderr).unwrap();
assert!(stderr.contains("Did not have permissions for all directories"));
}
#[test]
pub fn test_hidden_flag() {
// Check we can see the hidden file normally
let output = build_command(vec!["-c", "tests/test_dir_hidden_entries/"]);
assert!(output.contains(".hidden_file"));
assert!(output.contains("┌─┴ test_dir_hidden_entries"));
// Check that adding the '-h' flag causes us to not see hidden files
let output = build_command(vec!["-c", "-i", "tests/test_dir_hidden_entries/"]);
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 output = build_command(vec!["-c", "-f", "tests/test_dir"]);
assert!(output.contains("1 ┌── a_file "));
assert!(output.contains("1 ├── hello_file"));
assert!(output.contains("2 ┌─┴ many"));
assert!(output.contains("2 ┌─┴ test_dir"));
}
#[test]
pub fn test_show_files_by_type() {
// Check we can list files by type
let output = build_command(vec!["-c", "-t", "tests"]);
assert!(output.contains(" .unicode"));
assert!(output.contains(" .japan"));
assert!(output.contains(" .rs"));
assert!(output.contains(" (no extension)"));
assert!(output.contains("┌─┴ (total)"));
}
#[test]
pub fn test_output_skip_total() {
let output = build_command(vec![
"--skip-total",
"tests/test_dir/many/hello_file",
"tests/test_dir/many/a_file",
]);
assert!(output.contains("hello_file"));
assert!(!output.contains("(total)"));
}
#[test]
pub fn test_show_files_by_regex_match_lots() {
// Check we can see '.rs' files in the tests directory
let output = build_command(vec!["-c", "-e", "\\.rs$", "tests"]);
assert!(output.contains(" ┌─┴ tests"));
assert!(!output.contains("0B ┌── tests"));
assert!(!output.contains("0B ┌─┴ tests"));
}
#[test]
pub fn test_show_files_by_regex_match_nothing() {
// Check there are no files named: '.match_nothing' in the tests directory
let output = build_command(vec!["-c", "-e", "match_nothing$", "tests"]);
assert!(output.contains("0B ┌── tests"));
}
#[test]
pub fn test_show_files_by_regex_match_multiple() {
let output = build_command(vec![
"-c",
"-e",
"test_dir_hidden",
"-e",
"test_dir2",
"-n",
"100",
"tests",
]);
assert!(output.contains("test_dir2"));
assert!(output.contains("test_dir_hidden"));
assert!(!output.contains("many")); // We do not find the 'many' folder in the 'test_dir' folder
}
#[test]
pub fn test_show_files_by_invert_regex() {
let output = build_command(vec!["-c", "-f", "-v", "e", "tests/test_dir2"]);
// There are 0 files without 'e' in the name
assert!(output.contains("0 ┌── test_dir2"));
let output = build_command(vec!["-c", "-f", "-v", "a", "tests/test_dir2"]);
// There are 2 files without 'a' in the name
assert!(output.contains("2 ┌─┴ test_dir2"));
// There are 4 files in the test_dir2 hierarchy
let output = build_command(vec!["-c", "-f", "-v", "match_nothing$", "tests/test_dir2"]);
assert!(output.contains("4 ┌─┴ test_dir2"));
}
#[test]
pub fn test_show_files_by_invert_regex_match_multiple() {
// We ignore test_dir2 & test_dir_unicode, leaving the test_dir folder
// which has the 'many' folder inside
let output = build_command(vec![
"-c",
"-v",
"test_dir2",
"-v",
"test_dir_unicode",
"-n",
"100",
"tests",
]);
assert!(!output.contains("test_dir2"));
assert!(!output.contains("test_dir_unicode"));
assert!(output.contains("many"));
}

1
tests/tests.rs Normal file
View File

@@ -0,0 +1 @@

118
tests/tests_symlinks.rs Normal file
View File

@@ -0,0 +1,118 @@
use assert_cmd::Command;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::str;
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 build_temp_file(dir: &TempDir) -> PathBuf {
let file_path = dir.path().join("notes.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "I am a temp file").unwrap();
file_path
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_soft_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let file = build_temp_file(&dir);
let dir_s = dir.path().to_str().unwrap();
let file_path_s = file.to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg("-s")
.arg(file_path_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let c = format!(" ├── {}", link_name_s);
let b = format!(" ┌── {}", file_path_s);
let a = format!("─┴ {}", dir_s);
let mut cmd = Command::cargo_bin("dust").unwrap();
// Mac test runners create long filenames in tmp directories
let output = cmd
.args(["-p", "-c", "-s", "-w 999", 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)]
#[test]
pub fn test_hard_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let file = build_temp_file(&dir);
let dir_s = dir.path().to_str().unwrap();
let file_path_s = file.to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg(file_path_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let file_output = format!(" ┌── {}", file_path_s);
let dirs_output = format!("─┴ {}", dir_s);
let mut cmd = Command::cargo_bin("dust").unwrap();
// Mac test runners create long filenames in tmp directories
let output = cmd.args(["-p", "-c", "-w 999", dir_s]).unwrap().stdout;
// The link should not appear in the output because multiple inodes are now ordered
// then filtered.
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(dirs_output.as_str()));
assert!(output.contains(file_output.as_str()));
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_recursive_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let dir_s = dir.path().to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg("-s")
.arg(dir_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let a = format!("─┬ {}", dir_s);
let b = format!(" └── {}", link_name_s);
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-p")
.arg("-c")
.arg("-r")
.arg("-s")
.arg("-w 999")
.arg(dir_s)
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(a.as_str()));
assert!(output.contains(b.as_str()));
}