Compare commits

...

38 Commits

Author SHA1 Message Date
andy.boot 4e3c50177b hack 2021-09-19 15:43:40 +01:00
andy.boot 73bdb328f2 hack 2021-09-19 15:03:24 +01:00
andy.boot 68648178b3 hack 2021-09-19 14:44:31 +01:00
andy.boot 3c2149b431 release hacking:
drop version number back to 0.7.1, curious if i can reuse it when
testing
2021-09-19 14:37:38 +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
15 changed files with 557 additions and 331 deletions
+25 -4
View File
@@ -82,11 +82,11 @@ jobs:
job:
# { os, target, cargo-options, features, use-cross, toolchain }
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , use-cross: use-cross }
- { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-18.04 , target: i686-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-20.04 , target: i686-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-20.04 , target: i686-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-20.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-20.04 , target: x86_64-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-18.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-18.04 , target: x86_64-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-16.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
- { os: macos-latest , target: x86_64-apple-darwin }
- { os: windows-latest , target: i686-pc-windows-gnu }
- { os: windows-latest , target: i686-pc-windows-msvc }
@@ -205,6 +205,18 @@ jobs:
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
command: build
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
- name: Install cargo-deb
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-deb
if: ${{ contains(matrix.job.target, 'musl') }}
- name: Build deb
uses: actions-rs/cargo@v1
with:
command: deb
args: --no-build --target=${{ matrix.job.target }}
if: ${{ contains(matrix.job.target, 'musl') }}
- name: Test
uses: actions-rs/cargo@v1
with:
@@ -216,6 +228,12 @@ jobs:
with:
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
- name: Archive deb artifacts
uses: actions/upload-artifact@master
with:
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}.deb
path: target/${{ matrix.job.target }}/debian
if: ${{ contains(matrix.job.target, 'musl') }}
- name: Package
shell: bash
run: |
@@ -239,9 +257,12 @@ jobs:
with:
files: |
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
target/${{ matrix.job.target }}/debian
${{ env.PROJECT_NAME }}-${{ matrix.job.target }}.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
Generated
+36 -24
View File
@@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
@@ -29,9 +31,9 @@ dependencies = [
[[package]]
name = "assert_cmd"
version = "1.0.5"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88b6bd5df287567ffdf4ddf4d33060048e1068308e5f62d81c6f9824a045a48"
checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe"
dependencies = [
"bstr",
"doc-comment",
@@ -60,9 +62,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
@@ -109,9 +111,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if",
"crossbeam-epoch",
@@ -142,10 +144,10 @@ dependencies = [
]
[[package]]
name = "difference"
version = "2.0.0"
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "doc-comment"
@@ -155,14 +157,14 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "du-dust"
version = "0.5.4"
version = "0.7.1"
dependencies = [
"ansi_term 0.12.1",
"assert_cmd",
"clap",
"lscolors",
"num_cpus",
"rayon",
"regex",
"stfu8",
"tempfile",
"terminal_size",
@@ -190,13 +192,22 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.1.18"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "itertools"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -205,9 +216,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.97"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
[[package]]
name = "lscolors"
@@ -220,9 +231,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
@@ -251,11 +262,12 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "predicates"
version = "1.0.8"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df"
checksum = "c143348f141cc87aab5b950021bac6145d0e5ae754b0591de23244cee42c9308"
dependencies = [
"difference",
"difflib",
"itertools",
"predicates-core",
]
@@ -267,9 +279,9 @@ checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
[[package]]
name = "predicates-tree"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2"
checksum = "d7dd0fd014130206c9352efbdc92be592751b2b9274dff685348341082c6ea3d"
dependencies = [
"predicates-core",
"treeline",
@@ -342,9 +354,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
+16 -2
View File
@@ -1,9 +1,10 @@
[package]
name = "du-dust"
description = "A more intuitive version of du"
version = "0.5.4"
version = "0.7.1"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2018"
readme = "README.md"
documentation = "https://github.com/bootandy/dust"
homepage = "https://github.com/bootandy/dust"
@@ -24,12 +25,12 @@ path = "src/main.rs"
ansi_term = "0.12"
clap = { version = "=2.33", features = ["wrap_help"] }
lscolors = "0.7"
num_cpus = "1"
terminal_size = "0.1"
unicode-width = "0.1"
rayon="1"
thousands = "0.2"
stfu8 = "0.2"
regex = "1"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"
@@ -41,3 +42,16 @@ tempfile = "=3"
[[test]]
name = "integration"
path = "tests/tests.rs"
[package.metadata.deb]
section = "utils"
assets = [
["target/release/dust", "usr/bin/", "755"],
["LICENSE", "usr/share/doc/du-dust/", "644"],
["README.md", "usr/share/doc/du-dust/README", "644"],
]
extended-description = """\
Dust is meant to give you an instant overview of which directories are using
disk space without requiring sort or head. Dust will print a maximum of one
'Did not have permissions message'.
"""
+16 -8
View File
@@ -38,20 +38,27 @@ Dust is meant to give you an instant overview of which directories are using dis
Dust will list a slightly-less-than-the-terminal-height number of the biggest subdirectories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest subdirectories will be colored.
The different colors on the bars: These represent the combined tree hierarchy & disk usage. The shades of grey are used to indicate which parent folder a subfolder belongs to. For instance, look at the above screenshot. `.steam` is a folder taking 44% of the space. From the `.steam` bar is a light grey line that goes up. All these folders are inside `.steam` so if you delete `.steam` all that stuff will be gone too.
## Usage
```
Usage: dust
Usage: dust <dir>
Usage: dust <dir> <another_dir> <and_more>
Usage: dust -p <dir> (full-path - does not shorten the path of the subdirectories)
Usage: dust -s <dir> (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 <dir> (shows 30 directories instead of the default)
Usage: dust -d 3 <dir> (shows 3 levels of subdirectories)
Usage: dust -r <dir> (reverse order of output, with root at the lowest)
Usage: dust -x <dir> (only show directories on the same filesystem)
Usage: dust -X ignore <dir> (ignore all files and directories with the name 'ignore')
Usage: dust -b <dir> (do not show percentages or draw ASCII bars)
Usage: dust -p (full-path - Show fullpath of the subdirectories)
Usage: dust -s (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 (shows 30 directories instead of the default [default is terminal height])
Usage: dust -d 3 (shows 3 levels of subdirectories)
Usage: dust -r (reverse order of output)
Usage: dust -X ignore (ignore all files and directories with the name 'ignore')
Usage: dust -x (only show directories on the same filesystem)
Usage: dust -b (do not show percentages or draw ASCII bars)
Usage: dust -i (do not show hidden files)
Usage: dust -c (No colors [monochrome])
Usage: dust -f (Count files instead of diskspace)
Usage: dust -t Group by filetype
Usage: dust -e regex Only include files matching this regex (eg dust -e "\.png$" would match png files)
```
@@ -61,6 +68,7 @@ Usage: dust -b <dir> (do not show percentages or draw ASCII bars)
* [dutree](https://github.com/nachoparker/dutree)
* [dua](https://github.com/Byron/dua-cli/)
* [pdu](https://github.com/KSXGitHub/parallel-disk-usage)
* [dirstat-rs](https://github.com/scullionw/dirstat-rs)
* du -d 1 -h | sort -h
Note: Apparent-size is calculated slightly differently in dust to gdu. In dust each hard link is counted as using file_length space. In gdu only the first entry is counted.
+1 -1
View File
@@ -1,6 +1,6 @@
# ----------- To do a release ---------
# edit version in cargo.toml
# tag a commit and push (increment version first):
# tag a commit and push (increment version in Cargo.toml first):
# git tag v0.4.5
# git push origin v0.4.5
+67 -44
View File
@@ -1,8 +1,11 @@
use std::fs;
use crate::node::Node;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use regex::Regex;
use std::path::PathBuf;
use std::sync::atomic;
@@ -13,30 +16,29 @@ use std::collections::HashSet;
use crate::node::build_node;
use std::fs::DirEntry;
pub fn walk_it(
dirs: HashSet<PathBuf>,
ignore_directories: HashSet<PathBuf>,
use_apparent_size: bool,
by_filecount: bool,
ignore_hidden: bool,
) -> (Vec<Node>, bool) {
use crate::platform::get_metadata;
pub struct WalkData {
pub ignore_directories: HashSet<PathBuf>,
pub filter_regex: Option<Regex>,
pub invert_filter_regex: Option<Regex>,
pub allowed_filesystems: HashSet<u64>,
pub use_apparent_size: bool,
pub by_filecount: bool,
pub ignore_hidden: bool,
}
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> (Vec<Node>, bool) {
let permissions_flag = AtomicBool::new(false);
let top_level_nodes: Vec<_> = dirs
.into_iter()
.filter_map(|d| {
let n = walk(
d,
&permissions_flag,
&ignore_directories,
use_apparent_size,
by_filecount,
ignore_hidden,
);
let n = walk(d, &permissions_flag, &walk_data);
match n {
Some(n) => {
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
clean_inodes(n, &mut inodes, use_apparent_size)
clean_inodes(n, &mut inodes, walk_data.use_apparent_size)
}
None => None,
}
@@ -74,24 +76,39 @@ fn clean_inodes(
});
}
fn ignore_file(
entry: &DirEntry,
ignore_hidden: bool,
ignore_directories: &HashSet<PathBuf>,
) -> bool {
fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.');
let is_ignored_path = ignore_directories.contains(&entry.path());
(is_dot_file && ignore_hidden) || is_ignored_path
let is_ignored_path = walk_data.ignore_directories.contains(&entry.path());
if !walk_data.allowed_filesystems.is_empty() {
let size_inode_device = get_metadata(&entry.path(), false);
if let Some((_size, Some((_id, dev)))) = size_inode_device {
if !walk_data.allowed_filesystems.contains(&dev) {
return true;
}
}
}
// Keeping `walk_data.filter_regex.is_some()` is important for performance reasons, it stops unnecessary work
if walk_data.filter_regex.is_some()
&& 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_some()
&& entry.path().is_file()
&& is_filtered_out_due_to_invert_regex(&walk_data.invert_filter_regex, &entry.path())
{
return true;
}
(is_dot_file && walk_data.ignore_hidden) || is_ignored_path
}
fn walk(
dir: PathBuf,
permissions_flag: &AtomicBool,
ignore_directories: &HashSet<PathBuf>,
use_apparent_size: bool,
by_filecount: bool,
ignore_hidden: bool,
) -> Option<Node> {
fn walk(dir: PathBuf, permissions_flag: &AtomicBool, walk_data: &WalkData) -> Option<Node> {
let mut children = vec![];
if let Ok(entries) = fs::read_dir(dir.clone()) {
@@ -104,25 +121,22 @@ fn walk(
// rayon doesn't parallelise as well giving a 3X performance drop
// hence we unravel the recursion a bit
// return walk(entry.path(), permissions_flag, ignore_directories, use_apparent_size, by_filecount, ignore_hidden);
// return walk(entry.path(), permissions_flag, ignore_directories, allowed_filesystems, use_apparent_size, by_filecount, ignore_hidden);
if !ignore_file(&entry, ignore_hidden, &ignore_directories) {
if !ignore_file(entry, walk_data) {
if let Ok(data) = entry.file_type() {
if data.is_dir() && !data.is_symlink() {
return walk(
entry.path(),
permissions_flag,
ignore_directories,
use_apparent_size,
by_filecount,
ignore_hidden,
);
return walk(entry.path(), permissions_flag, walk_data);
}
return build_node(
entry.path(),
vec![],
use_apparent_size,
by_filecount,
&walk_data.filter_regex,
&walk_data.invert_filter_regex,
walk_data.use_apparent_size,
data.is_symlink(),
data.is_file(),
walk_data.by_filecount,
);
}
}
@@ -135,7 +149,16 @@ fn walk(
} else {
permissions_flag.store(true, atomic::Ordering::Relaxed);
}
build_node(dir, children, use_apparent_size, by_filecount)
build_node(
dir,
children,
&walk_data.filter_regex,
&walk_data.invert_filter_regex,
walk_data.use_apparent_size,
false,
false,
walk_data.by_filecount,
)
}
mod tests {
+5 -9
View File
@@ -107,7 +107,6 @@ impl DrawData<'_> {
#[allow(clippy::too_many_arguments)]
pub fn draw_it(
permission_error: bool,
use_full_path: bool,
is_reversed: bool,
no_colors: bool,
@@ -116,16 +115,13 @@ pub fn draw_it(
by_filecount: bool,
option_root_node: Option<DisplayNode>,
) {
if permission_error {
eprintln!("Did not have permissions for all directories");
}
if option_root_node.is_none() {
return;
}
let root_node = option_root_node.unwrap();
let num_chars_needed_on_left_most = if by_filecount {
let max_size = root_node.children.iter().map(|n| n.size).fold(0, max);
let max_size = root_node.size;
max_size.separate_with_commas().chars().count()
} else {
5 // Under normal usage we need 5 chars to display the size of a directory
@@ -265,9 +261,9 @@ fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &Display
// Add spaces after the filename so we can draw the % used bar chart.
let name_and_padding = name
+ &(repeat(" ")
.take(display_data.longest_string_length - width)
.collect::<String>());
+ " "
.repeat(display_data.longest_string_length - width)
.as_str();
maybe_trim_filename(name_and_padding, display_data)
}
@@ -320,7 +316,7 @@ fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayD
let size_as_str = node.size.separate_with_commas();
let spaces_to_add =
display_data.num_chars_needed_on_left_most - size_as_str.chars().count();
size_as_str + &*repeat(' ').take(spaces_to_add).collect::<String>()
size_as_str + " ".repeat(spaces_to_add).as_str()
} else {
format!("{:>5}", human_readable_number(node.size))
};
+6 -5
View File
@@ -36,11 +36,12 @@ impl DisplayNode {
}
pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = DisplayNode> {
if is_reversed {
let children: Vec<DisplayNode> = self.children.clone().into_iter().rev().collect();
children.into_iter()
// we box to avoid the clippy lint warning
let out: Box<dyn Iterator<Item = DisplayNode>> = if is_reversed {
Box::new(self.children.clone().into_iter().rev())
} else {
self.children.clone().into_iter()
}
Box::new(self.children.clone().into_iter())
};
out
}
}
+75 -5
View File
@@ -1,6 +1,7 @@
use crate::display_node::DisplayNode;
use crate::node::Node;
use std::collections::BinaryHeap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
@@ -13,7 +14,11 @@ pub fn get_by_depth(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode>
Some(build_by_depth(&root, n - 1))
}
pub fn get_biggest(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode> {
pub fn get_biggest(
top_level_nodes: Vec<Node>,
n: usize,
using_a_filter: bool,
) -> Option<DisplayNode> {
if top_level_nodes.is_empty() {
// perhaps change this, bring back Error object?
return None;
@@ -22,18 +27,17 @@ pub fn get_biggest(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode>
let mut heap = BinaryHeap::new();
let number_top_level_nodes = top_level_nodes.len();
let root = get_new_root(top_level_nodes);
root.children.iter().for_each(|c| heap.push(c));
let mut allowed_nodes = HashSet::new();
allowed_nodes.insert(&root.name);
heap = add_children(using_a_filter, &root, heap);
for _ in number_top_level_nodes..n {
let line = heap.pop();
match line {
Some(line) => {
line.children.iter().for_each(|c| heap.push(c));
allowed_nodes.insert(&line.name);
heap = add_children(using_a_filter, line, heap);
}
None => break,
}
@@ -41,6 +45,72 @@ pub fn get_biggest(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode>
recursive_rebuilder(&allowed_nodes, &root)
}
pub fn get_all_file_types(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode> {
let mut map: HashMap<String, DisplayNode> = HashMap::new();
build_by_all_file_types(top_level_nodes, &mut map);
let mut by_types: Vec<DisplayNode> = map.into_iter().map(|(_k, v)| v).collect();
by_types.sort();
by_types.reverse();
let displayed = if by_types.len() <= n {
by_types
} else {
let (displayed, rest) = by_types.split_at(if n > 1 { n - 1 } else { 1 });
let remaining = DisplayNode {
name: PathBuf::from("(others)"),
size: rest.iter().map(|a| a.size).sum(),
children: vec![],
};
let mut displayed = displayed.to_vec();
displayed.push(remaining);
displayed
};
let result = DisplayNode {
name: PathBuf::from("(total)"),
size: displayed.iter().map(|a| a.size).sum(),
children: displayed,
};
Some(result)
}
fn add_children<'a>(
using_a_filter: bool,
line: &'a Node,
mut heap: BinaryHeap<&'a Node>,
) -> BinaryHeap<&'a Node> {
if using_a_filter {
line.children.iter().for_each(|c| {
if c.name.is_file() || c.size > 0 {
heap.push(c)
}
});
} else {
line.children.iter().for_each(|c| heap.push(c));
}
heap
}
fn build_by_all_file_types(top_level_nodes: Vec<Node>, counter: &mut HashMap<String, DisplayNode>) {
for node in top_level_nodes {
if node.name.is_file() {
let ext = node.name.extension();
let key: String = match ext {
Some(e) => ".".to_string() + &e.to_string_lossy(),
None => "(no extension)".into(),
};
let mut display_node = counter.entry(key.clone()).or_insert(DisplayNode {
name: PathBuf::from(key),
size: 0,
children: vec![],
});
display_node.size += node.size;
}
build_by_all_file_types(node.children, counter)
}
}
fn build_by_depth(node: &Node, depth: usize) -> DisplayNode {
let new_children = {
if depth == 0 {
+110 -20
View File
@@ -1,20 +1,25 @@
#[macro_use]
extern crate clap;
extern crate rayon;
extern crate regex;
extern crate unicode_width;
use std::collections::HashSet;
use std::process;
use self::display::draw_it;
use clap::{App, AppSettings, Arg};
use dirwalker::walk_it;
use filter::{get_biggest, get_by_depth};
use dir_walker::walk_it;
use dir_walker::WalkData;
use filter::{get_all_file_types, get_biggest, get_by_depth};
use regex::Regex;
use std::cmp::max;
use std::path::PathBuf;
use terminal_size::{terminal_size, Height, Width};
use utils::get_filesystem_devices;
use utils::simplify_dir_names;
mod dirwalker;
mod dir_walker;
mod display;
mod display_node;
mod filter;
@@ -59,6 +64,7 @@ fn get_height_of_terminal() -> usize {
}
}
#[cfg(windows)]
fn get_width_of_terminal() -> usize {
// Windows CI runners detect a very low terminal width
if let Some((Width(w), Height(_h))) = terminal_size() {
@@ -68,6 +74,28 @@ fn get_width_of_terminal() -> usize {
}
}
#[cfg(not(windows))]
fn get_width_of_terminal() -> usize {
if let Some((Width(w), Height(_h))) = terminal_size() {
w as usize
} else {
DEFAULT_TERMINAL_WIDTH
}
}
fn get_regex_value(maybe_value: Option<&str>) -> Option<Regex> {
match maybe_value {
Some(v) => match Regex::new(v) {
Ok(r) => Some(r),
Err(e) => {
eprintln!("Ignoring bad value for regex {:?}", e);
process::exit(1);
}
},
None => None,
}
}
fn main() {
let default_height = get_height_of_terminal();
let def_num_str = default_height.to_string();
@@ -107,6 +135,12 @@ fn main() {
.multiple(true)
.help("Exclude any file or directory with this name"),
)
.arg(
Arg::with_name("limit_filesystem")
.short("x")
.long("limit-filesystem")
.help("Only count the files and directories on the same filesystem as the supplied directory"),
)
.arg(
Arg::with_name("display_apparent_size")
.short("s")
@@ -140,8 +174,38 @@ fn main() {
.arg(
Arg::with_name("ignore_hidden")
.short("i") // Do not use 'h' this is used by 'help'
.long("ignore_hidden")
.help("Obey .git_ignore rules & Do not display hidden files"),
.long("ignore_hidden") //TODO: fix change - -> _
.help("Do not display hidden files"),
)
.arg(
Arg::with_name("invert_filter")
.short("v")
.long("invert-filter")
.takes_value(true)
.number_of_values(1)
.multiple(true)
.conflicts_with("filter")
.conflicts_with("types")
.conflicts_with("depth")
.help("Exclude files matching this regex. To ignore png files type: -v \"\\.png$\" "),
)
.arg(
Arg::with_name("filter")
.short("e")
.long("filter")
.takes_value(true)
.number_of_values(1)
.multiple(true)
.conflicts_with("types")
.conflicts_with("depth")
.help("Only include files matching this regex. For png files type: -e \"\\.png$\" "),
)
.arg(
Arg::with_name("types")
.short("t")
.long("file_types")
.conflicts_with("depth")
.help("show only these file types"),
)
.arg(
Arg::with_name("width")
@@ -151,15 +215,18 @@ fn main() {
.number_of_values(1)
.help("Specify width of output overriding the auto detection of terminal width"),
)
.arg(Arg::with_name("inputs").multiple(true))
.arg(Arg::with_name("inputs").multiple(true).default_value("."))
.get_matches();
let target_dirs = {
match options.values_of("inputs") {
None => vec!["."],
Some(r) => r.collect(),
}
};
let target_dirs = options
.values_of("inputs")
.expect("Should be a default value here")
.collect();
let summarize_file_types = options.is_present("types");
let maybe_filter = get_regex_value(options.value_of("filter"));
let maybe_invert_filter = get_regex_value(options.value_of("invert_filter"));
let number_of_lines = match value_t!(options.value_of("number_of_lines"), usize) {
Ok(v) => v,
@@ -191,31 +258,54 @@ fn main() {
let by_filecount = options.is_present("by_filecount");
let ignore_hidden = options.is_present("ignore_hidden");
let limit_filesystem = options.is_present("limit_filesystem");
let simplified_dirs = simplify_dir_names(target_dirs);
let allowed_filesystems = {
if limit_filesystem {
get_filesystem_devices(simplified_dirs.iter())
} else {
HashSet::new()
}
};
let ignored_full_path: HashSet<PathBuf> = ignore_directories
.into_iter()
.flat_map(|x| simplified_dirs.iter().map(move |d| d.join(x.clone())))
.collect();
let (nodes, errors) = walk_it(
simplified_dirs,
ignored_full_path,
let walk_data = WalkData {
ignore_directories: ignored_full_path,
filter_regex: maybe_filter,
invert_filter_regex: maybe_invert_filter,
allowed_filesystems,
use_apparent_size,
by_filecount,
ignore_hidden,
);
};
let (top_level_nodes, has_errors) = walk_it(simplified_dirs, walk_data);
let tree = {
match depth {
None => get_biggest(nodes, number_of_lines),
Some(depth) => get_by_depth(nodes, depth),
match (depth, summarize_file_types) {
(_, true) => get_all_file_types(top_level_nodes, number_of_lines),
(Some(depth), _) => get_by_depth(top_level_nodes, depth),
(_, _) => get_biggest(
top_level_nodes,
number_of_lines,
options.values_of("filter").is_some()
|| options.value_of("invert_filter").is_some(),
),
}
};
if options.is_present("filter") {
println!("Filtering by: {}", options.value_of("filter").unwrap());
}
if has_errors {
eprintln!("Did not have permissions for all directories");
}
draw_it(
errors,
options.is_present("display_full_paths"),
!options.is_present("reverse"),
no_colors,
+26 -1
View File
@@ -1,5 +1,8 @@
use crate::platform::get_metadata;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;
use regex::Regex;
use std::cmp::Ordering;
use std::path::PathBuf;
@@ -11,15 +14,37 @@ pub struct Node {
pub inode_device: Option<(u64, u64)>,
}
#[allow(clippy::too_many_arguments)]
pub fn build_node(
dir: PathBuf,
children: Vec<Node>,
filter_regex: &Option<Regex>,
invert_filter_regex: &Option<Regex>,
use_apparent_size: bool,
is_symlink: bool,
is_file: bool,
by_filecount: bool,
) -> Option<Node> {
match get_metadata(&dir, use_apparent_size) {
Some(data) => {
let (size, inode_device) = if by_filecount { (1, data.1) } else { data };
let inode_device = if is_symlink && !use_apparent_size {
None
} else {
data.1
};
let size = if is_filtered_out_due_to_regex(filter_regex, &dir)
|| is_filtered_out_due_to_invert_regex(invert_filter_regex, &dir)
|| (is_symlink && !use_apparent_size)
|| by_filecount && !is_file
{
0
} else if by_filecount {
1
} else {
data.0
};
Some(Node {
name: dir,
size,
+2 -2
View File
@@ -120,9 +120,9 @@ pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(
{
Some((md.len(), None))
} else {
get_metadata_expensive(&d)
get_metadata_expensive(d)
}
}
_ => get_metadata_expensive(&d),
_ => get_metadata_expensive(d),
}
}
+39 -5
View File
@@ -1,11 +1,9 @@
use platform::get_metadata;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
let parent = parent.as_ref();
let child = child.as_ref();
child.starts_with(parent) && !parent.starts_with(child)
}
use crate::platform;
use regex::Regex;
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
let mut top_level_names: HashSet<PathBuf> = HashSet::with_capacity(filenames.len());
@@ -33,6 +31,22 @@ pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf>
top_level_names
}
pub fn get_filesystem_devices<'a, P: IntoIterator<Item = &'a PathBuf>>(paths: P) -> HashSet<u64> {
// Gets the device ids for the filesystems which are used by the argument paths
paths
.into_iter()
.filter_map(|p| {
let meta = get_metadata(p, false);
if let Some((_size, Some((_id, dev)))) = meta {
Some(dev)
} else {
None
}
})
.collect()
}
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
// normalize path ...
// 1. removing repeated separators
@@ -43,6 +57,26 @@ pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
path.as_ref().components().collect::<PathBuf>()
}
pub fn is_filtered_out_due_to_regex(filter_regex: &Option<Regex>, dir: &Path) -> bool {
match filter_regex {
Some(fr) => !fr.is_match(&dir.as_os_str().to_string_lossy()),
None => false,
}
}
pub fn is_filtered_out_due_to_invert_regex(filter_regex: &Option<Regex>, dir: &Path) -> bool {
match filter_regex {
Some(fr) => fr.is_match(&dir.as_os_str().to_string_lossy()),
None => false,
}
}
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::*;
+56 -132
View File
@@ -1,4 +1,5 @@
use assert_cmd::Command;
use std::ffi::OsStr;
use std::str;
use std::sync::Once;
@@ -37,7 +38,7 @@ fn copy_test_data(dir: &str) {
};
}
pub fn initialize() {
fn initialize() {
INIT.call_once(|| {
copy_test_data("tests/test_dir");
copy_test_data("tests/test_dir2");
@@ -45,160 +46,101 @@ pub fn initialize() {
});
}
fn exact_output_test<T: AsRef<OsStr>>(valid_outputs: Vec<String>, command_args: Vec<T>) {
initialize();
let mut a = &mut Command::cargo_bin("dust").unwrap();
for p in command_args {
a = a.arg(p);
}
let output: String = str::from_utf8(&a.unwrap().stdout).unwrap().into();
assert!(valid_outputs
.iter()
.fold(false, |sum, i| sum || output.contains(i)));
}
// "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_basic() {
// -c is no color mode - This makes testing much simpler
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd.arg("-c").arg("/tmp/test_dir/").unwrap().stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output()));
exact_output_test(main_output(), vec!["-c", "/tmp/test_dir/"])
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_multi_arg() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd
.arg("-c")
.arg("/tmp/test_dir/many/")
.arg("/tmp/test_dir")
.arg("/tmp/test_dir")
.unwrap()
.stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output()));
let command_args = vec![
"-c",
"/tmp/test_dir/many/",
"/tmp/test_dir",
"/tmp/test_dir",
];
exact_output_test(main_output(), command_args);
}
#[cfg(target_os = "macos")]
fn main_output() -> String {
r#"
fn main_output() -> Vec<String> {
// Some linux currently thought to be Manjaro, Arch
// Although probably depends on how drive is formatted
let mac_and_some_linux = r#"
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ many │████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
.to_string();
#[cfg(target_os = "linux")]
fn main_output() -> String {
r#"
let ubuntu = r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33%
8.0K ┌─┴ many │ █████████████████████████████████ │ 67%
12K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
.to_string();
#[cfg(target_os = "windows")]
fn main_output() -> String {
"windows results vary by host".to_string()
vec![mac_and_some_linux, ubuntu]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_long_paths() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd
.arg("-c")
.arg("-p")
.arg("/tmp/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output_long_paths()));
let command_args = vec!["-c", "-p", "/tmp/test_dir/"];
exact_output_test(main_output_long_paths(), command_args);
}
#[cfg(target_os = "macos")]
fn main_output_long_paths() -> String {
r#"
fn main_output_long_paths() -> Vec<String> {
let mac_and_some_linux = r#"
0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── /tmp/test_dir/many/hello_file│█████████████████████████████ │ 100%
4.0K ┌─┴ /tmp/test_dir/many │█████████████████████████████ │ 100%
4.0K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "linux")]
fn main_output_long_paths() -> String {
r#"
.to_string();
let ubuntu = r#"
0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░██████████ │ 33%
8.0K ┌─┴ /tmp/test_dir/many │ ████████████████████ │ 67%
12K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "windows")]
fn main_output_long_paths() -> String {
"windows results vary by host".to_string()
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_apparent_size() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd.arg("-c").arg("-s").arg("/tmp/test_dir").unwrap().stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&output_apparent_size()));
}
#[cfg(target_os = "linux")]
fn output_apparent_size() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ┌─┴ many │ █████████████████████████ │ 50%
8.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "macos")]
fn output_apparent_size() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░░░██ │ 3%
134B ┌─┴ many │ ████████████████████████████ │ 58%
230B ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "windows")]
fn output_apparent_size() -> String {
"windows results vary by host".to_string()
.to_string();
vec![mac_and_some_linux, ubuntu]
}
// Check against directories and files whos names are substrings of each other
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_substring_of_names_and_long_names() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-c").arg("/tmp/test_dir2").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(&no_substring_of_names_output()));
let command_args = vec!["-c", "/tmp/test_dir2"];
exact_output_test(no_substring_of_names_output(), command_args);
}
#[cfg(target_os = "linux")]
fn no_substring_of_names_output() -> String {
"
fn no_substring_of_names_output() -> Vec<String> {
let ubuntu = "
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
4.0K ├── dir_name_clash
4.0K │ ┌── hello
@@ -208,12 +150,9 @@ fn no_substring_of_names_output() -> String {
24K ┌─┴ test_dir2
"
.trim()
.into()
}
.into();
#[cfg(target_os = "macos")]
fn no_substring_of_names_output() -> String {
"
let mac_and_some_linux = "
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
4.0K │ ┌── hello
4.0K ├─┴ dir
@@ -223,48 +162,33 @@ fn no_substring_of_names_output() -> String {
12K ┌─┴ test_dir2
"
.trim()
.into()
}
#[cfg(target_os = "windows")]
fn no_substring_of_names_output() -> String {
"PRs".into()
.into();
vec![mac_and_some_linux, ubuntu]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_unicode_directories() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-c").arg("/tmp/test_dir_unicode").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(&unicode_dir()));
let command_args = vec!["-c", "/tmp/test_dir_unicode"];
exact_output_test(unicode_dir(), command_args);
}
#[cfg(target_os = "linux")]
fn unicode_dir() -> String {
fn unicode_dir() -> Vec<String> {
// The way unicode & asian characters are rendered on the terminal should make this line up
"
let ubuntu = "
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
0B ├── 👩.unicode │ █ │ 0%
4.0K ┌─┴ test_dir_unicode │██████████████████████████████████ │ 100%
"
.trim()
.into()
}
.into();
#[cfg(target_os = "macos")]
fn unicode_dir() -> String {
"
let mac_and_some_linux = "
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
0B ├── 👩.unicode │ █ │ 0%
0B ┌─┴ test_dir_unicode │ █ │ 0%
"
.trim()
.into()
}
#[cfg(target_os = "windows")]
fn unicode_dir() -> String {
"".into()
.into();
vec![mac_and_some_linux, ubuntu]
}
+77 -69
View File
@@ -1,17 +1,25 @@
use assert_cmd::Command;
use std::ffi::OsStr;
use std::str;
/**
* This file contains tests that test a substring of the output using '.contains'
*
* These tests should be the same cross platform
*/
fn build_command<T: AsRef<OsStr>>(command_args: Vec<T>) -> String {
let mut a = &mut Command::cargo_bin("dust").unwrap();
for p in command_args {
a = a.arg(p);
}
str::from_utf8(&a.unwrap().stdout).unwrap().into()
}
// We can at least test the file names are there
#[test]
pub fn test_basic_output() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("tests/test_dir/").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["tests/test_dir/"]);
assert!(output.contains(" ┌─┴ "));
assert!(output.contains("test_dir "));
@@ -25,9 +33,7 @@ pub fn test_basic_output() {
#[test]
pub fn test_output_no_bars_means_no_excess_spaces() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-b").arg("tests/test_dir/").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-b", "tests/test_dir/"]);
// If bars are not being shown we don't need to pad the output with spaces
assert!(output.contains("many"));
assert!(!output.contains("many "));
@@ -35,15 +41,7 @@ pub fn test_output_no_bars_means_no_excess_spaces() {
#[test]
pub fn test_reverse_flag() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-r")
.arg("tests/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-r", "-c", "tests/test_dir/"]);
assert!(output.contains(" └─┬ test_dir "));
assert!(output.contains(" └─┬ many "));
assert!(output.contains(" ├── hello_file"));
@@ -53,15 +51,7 @@ pub fn test_reverse_flag() {
#[test]
pub fn test_d_flag_works() {
// We should see the top level directory but not the sub dirs / files:
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-d")
.arg("1")
.arg("-s")
.arg("tests/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-d", "1", "tests/test_dir/"]);
assert!(!output.contains("hello_file"));
}
@@ -69,31 +59,14 @@ pub fn test_d_flag_works() {
pub fn test_d_flag_works_and_still_recurses_down() {
// We had a bug where running with '-d 1' would stop at the first directory and the code
// would fail to recurse down
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-d")
.arg("1")
.arg("-f")
.arg("-c")
.arg("tests/test_dir2/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains("7 ┌─┴ test_dir2"));
let output = build_command(vec!["-d", "1", "-f", "-c", "tests/test_dir2/"]);
assert!(output.contains("4 ┌─┴ test_dir2"));
}
// Check against directories and files whos names are substrings of each other
#[test]
pub fn test_ignore_dir() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-X")
.arg("dir_substring")
.arg("tests/test_dir2")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-c", "-X", "dir_substring", "tests/test_dir2/"]);
assert!(!output.contains("dir_substring"));
}
@@ -108,25 +81,12 @@ pub fn test_with_bad_param() {
#[test]
pub fn test_hidden_flag() {
// Check we can see the hidden file normally
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("tests/test_dir_hidden_entries")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-c", "tests/test_dir_hidden_entries/"]);
assert!(output.contains(".hidden_file"));
assert!(output.contains("┌─┴ test_dir_hidden_entries"));
// Check that adding the '-h' flag causes us to not see hidden files
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-i")
.arg("tests/test_dir_hidden_entries")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-c", "-i", "tests/test_dir_hidden_entries/"]);
assert!(!output.contains(".hidden_file"));
assert!(output.contains("┌── test_dir_hidden_entries"));
}
@@ -134,16 +94,64 @@ pub fn test_hidden_flag() {
#[test]
pub fn test_number_of_files() {
// Check we can see the hidden file normally
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-f")
.arg("tests/test_dir")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
let output = build_command(vec!["-c", "-f", "tests/test_dir"]);
assert!(output.contains("1 ┌── a_file "));
assert!(output.contains("1 ├── hello_file"));
assert!(output.contains("3 ┌─┴ many"));
assert!(output.contains("4 ┌─┴ test_dir"));
assert!(output.contains("2 ┌─┴ many"));
assert!(output.contains("2 ┌─┴ test_dir"));
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_apparent_size() {
// Check the '-s' Flag gives us byte sizes and that it doesn't round up to a block
let command_args = vec!["-c", "-s", "/tmp/test_dir"];
let output = build_command(command_args);
let apparent_size1 = "6B ├── hello_file│";
let apparent_size2 = "0B ┌── a_file";
assert!(output.contains(apparent_size1));
assert!(output.contains(apparent_size2));
let incorrect_apparent_size = "4.0K ├── hello_file";
assert!(!output.contains(incorrect_apparent_size));
}
#[test]
pub fn test_show_files_by_type() {
// Check we can list files by type
let output = build_command(vec!["-c", "-t", "tests"]);
assert!(output.contains(" .unicode"));
assert!(output.contains(" .japan"));
assert!(output.contains(" .rs"));
assert!(output.contains(" (no extension)"));
assert!(output.contains("┌─┴ (total)"));
}
#[test]
pub fn test_show_files_by_regex() {
// 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"));
// 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_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"));
}