diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 1898009..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,83 +0,0 @@
-# Based on the "trust" template v0.1.2
-# https://github.com/japaric/trust/tree/v0.1.2
-
-# ----------- To do a release ---------
-# tag a commit and push:
-# git tag v0.4.0.1
-# git push origin v0.4.0.1
-# Remember to do a cargo publish to put it in crates.io
-
-dist: trusty
-language: rust
-services: docker
-sudo: required
-
-# TODO Rust builds on stable by default, this can be
-# overridden on a case by case basis down below.
-
-env:
- global:
- # TODO Update this to match the name of your project.
- - CRATE_NAME=dust
-
-matrix:
- # TODO These are all the build jobs. Adjust as necessary. Comment out what you
- # don't need
- include:
- # Linux
- - env: TARGET=x86_64-unknown-linux-gnu
-
- # OSX
- - env: TARGET=x86_64-apple-darwin
- os: osx
-
-before_install:
- - set -e
- - rustup self update
-
-install:
- - sh ci/install.sh
- - source ~/.cargo/env || true
-
-script:
- - bash ci/script.sh
-
-after_script: set +e
-
-before_deploy:
- - sh ci/before_deploy.sh
-
-deploy:
- # TODO update `api_key.secure`
- # - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new
- # - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789
- # - Paste the output down here
-# api_key:
-# secure: UlU73Td7Bkb2N88ws4YGLWR+4U0IMgiou9QQtMnmpouJFjeUNxtLSPMPODVXP7zq4sKt5HR5B3fX9MW4mKm351fvnQEoihETn06pKiXGnY//SlTPTt67MX9ZOYmd9ohJReMDOZDgqhnGLxfymycGtsLAmdjDZnAl+IMqgg0FMyVFj9Cl9aKxnn12lxQyX4zabHKk8TUKD3By8ZoEUnJMHt3gEtOmbDgS4brcTPeHCzqnYFw73LEnkqvz+JP0XwauJY7Cf8lminKm/klmjCkQji8T9SHI52v1g0Fxpx0ucp2o3vulQrLHXaHvZ6Fr7J0cSXXzaFF3rrGLt4t4jU/+9TZm1+n5k5XuPW4x4NTCC9NmIj/z0/z41t82E9qZhzhtm2Jdsg6H2tNk+C774TYqcmR6GCvfRadfjRp3cA5dh0UwDVjH2MJFxlHDVkl6la0mVVRsCGF3oBKZVk0BDl1womfnmI46o/uU+gLknHN6Ed6PHHPPYDViWd3VKdmHKT7XrkMMUF6HjZUtla689DWIOWZSiV++1dVPcl/1TV+6tTmN4bBtPcLuX7SHRuLp2PI2kATvRMECsa7gZRypW4jKpVn7b2yetX9TVI3i1zR5zkQJ3dPg8sATvYPL53aKH/WsqUg4rzoAlbk9so+++R4bQY69LhV3B511B7EAynoZFdM
- api_key: $API_KEY
- file_glob: true
- file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.*
- on:
- # TODO Here you can pick which targets will generate binary releases
- # In this example, there are some targets that are tested using the stable
- # and nightly channels. This condition makes sure there is only one release
- # for such targets and that's generated using the stable channel
- condition: $TRAVIS_RUST_VERSION = stable
- tags: true
- provider: releases
- skip_cleanup: true
-
-cache: cargo
-before_cache:
- # Travis can't cache files that are not readable by "others"
- - chmod -R a+r $HOME/.cargo
-
-branches:
- only:
- # release tags
- - /^v\d+\.\d+\.\d+.*$/
- - master
-
-notifications:
- email:
- on_success: never
diff --git a/Cargo.lock b/Cargo.lock
index fbcbc8b..e679baa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -26,7 +26,7 @@ dependencies = [
"environment 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -46,7 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
-version = "0.3.42"
+version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -188,6 +188,8 @@ dependencies = [
"jwalk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -206,7 +208,7 @@ name = "failure"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "backtrace 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -217,7 +219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -241,7 +243,7 @@ dependencies = [
[[package]]
name = "itoa"
-version = "0.4.4"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -416,10 +418,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_json"
-version = "1.0.44"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -431,7 +433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -446,7 +448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -463,6 +465,15 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
+[[package]]
+name = "terminal_size"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[[package]]
name = "textwrap"
version = "0.11.0"
@@ -524,7 +535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a29ab7c0ed62970beb0534d637a8688842506d0ff9157de83286dacd065c8149"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
-"checksum backtrace 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b4b1549d804b6c73f4817df2ba073709e96e426f12987127c48e6745568c350b"
+"checksum backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e4036b9bf40f3cf16aba72a3d65e8a520fc4bafcdc7079aea8f848c58c5b5536"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
@@ -545,7 +556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
-"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
+"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
"checksum jwalk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3dbf0a8f61baee43a2918ff50ac6a2d3b2c105bc08ed53bc298779f1263409"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
@@ -569,11 +580,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
-"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
+"checksum serde_json 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "15913895b61e0be854afd32fd4163fcd2a3df34142cf2cb961b310ce694cbf90"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
-"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
+"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+"checksum terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "e25a60e3024df9029a414be05f46318a77c22538861a22170077d0388c0e926e"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
diff --git a/Cargo.toml b/Cargo.toml
index fa60fbf..cc85647 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,8 @@ ansi_term = "=0.12"
clap = "=2.33"
jwalk = "0.4.0"
num_cpus = "1.12"
+terminal_size = "0.1.10"
+unicode-width = "0.1.7"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"
@@ -32,3 +34,8 @@ winapi-util = "0.1"
[dev-dependencies]
assert_cli = "=0.6"
tempfile = "=3"
+
+
+[[test]]
+name = "integration"
+path = "tests/tests.rs"
diff --git a/README.md b/README.md
index ce7403e..4212dc4 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ du + rust = dust. Like du but more intuitive
Dust is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust will print a maximum of 1 'Did not have permissions message'.
-Dust will list the 20 biggest sub directories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest sub directory will have its size shown in *red*
+Dust will list the terminal height - 10 biggest sub directories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest sub directory will have its size shown in *red*
## Why?
@@ -42,26 +42,43 @@ Usage: dust -d 3
(Shows 3 levels of subdirectories)
Usage: dust -r (Reverse order of output, with root at the lowest)
Usage: dust -x (Only show directories on same filesystem)
Usage: dust -X ignore (Ignore all files and directories with the name 'ignore')
+Usage: dust -b (Do not show percentages or draw the ASCII bars)
```
```
-djin:git/dust> dust
- 1.2G target
- 622M ├─┬ debug
- 445M │ ├── deps
- 70M │ ├── incremental
- 56M │ └── build
- 262M ├─┬ rls
- 262M │ └─┬ debug
- 203M │ ├── deps
- 56M │ └── build
- 165M ├─┬ package
- 165M │ └─┬ du-dust-0.2.4
- 165M │ └─┬ target
- 165M │ └─┬ debug
- 131M │ └── deps
- 165M └─┬ release
- 124M └── deps
+$ dust target
+ 15M ┌── build │ ░█ │ 2%
+ 25M ├── deps │ ░█ │ 4%
+ 45M ┌─┴ release │ ██ │ 7%
+ 84M │ ┌── build │ ▒▒▒▒▒████ │ 13%
+ 7.6M │ │ ┌── libsynstructure-f7552412787ad339.rlib│ ▒▒▒▓▓▓▓▓█ │ 1%
+ 16M │ │ ├── libfailure_derive-e18365d3e6be2e2c.so│ ▒▒▒▓▓▓▓▓█ │ 2%
+ 18M │ │ ├── libsyn-9ad95b745845d5dd.rlib │ ▒▒▒▓▓▓▓▓█ │ 3%
+ 19M │ │ ├── libsyn-d4a3458fcb1c592c.rlib │ ▒▒▒▓▓▓▓▓█ │ 3%
+ 135M │ ├─┴ deps │ ▒▒▒██████ │ 20%
+ 228M │ ┌─┴ debug │ █████████ │ 34%
+ 228M ├─┴ rls │ █████████ │ 34%
+ 18M │ ┌── dust │ ░░░░░░░░░░░░░░█ │ 3%
+ 22M │ ├── dust-a0c31c4633c5fc8b │ ░░░░░░░░░░░░░░█ │ 3%
+ 7.4M │ │ ┌── s-fkrj3vfncf-19aj951-1fv3o6tzvr348 │ ░░░░░░░░░░░░░▒█ │ 1%
+ 7.4M │ │ ┌─┴ dust-1i3xquz5fns51 │ ░░░░░░░░░░░░░▒█ │ 1%
+ 40M │ ├─┴ incremental │ ░░░░░░░░░░░░░██ │ 6%
+ 41M │ ├── build │ ░░░░░░░░░░░░░██ │ 6%
+ 7.6M │ │ ┌── libsynstructure-f7552412787ad339.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
+ 8.2M │ │ ├── libserde-ab4b407a415bc8fc.rmeta │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
+ 9.4M │ │ ├── libserde-ab4b407a415bc8fc.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
+ 11M │ │ ├── tests_symlinks-bf063461b7be6a99 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
+ 11M │ │ ├── integration-08f999d253e3b70c │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
+ 15M │ │ ├── dust-1c6e63725d641738 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
+ 16M │ │ ├── libfailure_derive-e18365d3e6be2e2c.so │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
+ 18M │ │ ├── dust-3a419f62b84d73c1 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
+ 18M │ │ ├── dust-2bdf724d4a721d31 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
+ 18M │ │ ├── libsyn-9ad95b745845d5dd.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
+ 23M │ │ ├── libclap-0dedc35af3ef0670.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
+ 267M │ ├─┴ deps │ ░░░░███████████ │ 40%
+ 392M ├─┴ debug │ ███████████████ │ 59%
+ 667M ┌─┴ target │█████████████████████████ │ 100%
+
```
diff --git a/ci/how2publish.txt b/ci/how2publish.txt
new file mode 100644
index 0000000..1c7f038
--- /dev/null
+++ b/ci/how2publish.txt
@@ -0,0 +1,6 @@
+# ----------- To do a release ---------
+# tag a commit and push:
+# git tag v0.4.5
+# git push origin v0.4.5
+
+# cargo publish to put it in crates.io
\ No newline at end of file
diff --git a/src/display.rs b/src/display.rs
index 43ae0ca..977cd7e 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -4,34 +4,32 @@ use self::ansi_term::Colour::Fixed;
use self::ansi_term::Style;
use crate::utils::Node;
+use terminal_size::{terminal_size, Height, Width};
+
+use unicode_width::UnicodeWidthStr;
+
+use std::cmp::max;
+use std::cmp::min;
+use std::iter::repeat;
use std::path::Path;
static UNITS: [char; 4] = ['T', 'G', 'M', 'K'];
+static BLOCKS: [char; 5] = ['█', '▓', '▒', '░', ' '];
+static DEFAULT_TERMINAL_WIDTH: u16 = 80;
pub struct DisplayData {
pub short_paths: bool,
pub is_reversed: bool,
pub colors_on: bool,
+ pub base_size: u64,
+ pub longest_string_length: usize,
}
impl DisplayData {
- fn get_first_chars(&self) -> &str {
- if self.is_reversed {
- "─┴"
- } else {
- "─┬"
- }
- }
-
#[allow(clippy::collapsible_if)]
- fn get_tree_chars(
- &self,
- num_siblings: u64,
- max_siblings: u64,
- has_children: bool,
- ) -> &'static str {
+ fn get_tree_chars(&self, was_i_last: bool, has_children: bool) -> &'static str {
if self.is_reversed {
- if num_siblings == max_siblings - 1 {
+ if was_i_last {
if has_children {
"┌─┴"
} else {
@@ -43,7 +41,7 @@ impl DisplayData {
"├──"
}
} else {
- if num_siblings == 0 {
+ if was_i_last {
if has_children {
"└─┬"
} else {
@@ -57,22 +55,82 @@ impl DisplayData {
}
}
- fn is_biggest(&self, num_siblings: u64, max_siblings: u64) -> bool {
+ fn is_biggest(&self, num_siblings: usize, max_siblings: u64) -> bool {
if self.is_reversed {
- num_siblings == 0
+ num_siblings == (max_siblings - 1) as usize
} else {
- num_siblings == max_siblings - 1
+ num_siblings == 0
}
}
- fn get_children_from_node(&self, node: Node) -> impl Iterator- {
+ fn is_last(&self, num_siblings: usize, max_siblings: u64) -> bool {
if self.is_reversed {
- let n: Vec = node.children.into_iter().rev().map(|a| a).collect();
- n.into_iter()
+ num_siblings == 0
} else {
- node.children.into_iter()
+ num_siblings == (max_siblings - 1) as usize
}
}
+
+ fn percent_size(&self, node: &Node) -> f32 {
+ let result = node.size as f32 / self.base_size as f32;
+ if result.is_normal() {
+ result
+ } else {
+ 0.0
+ }
+ }
+}
+
+fn get_children_from_node(node: Node, is_reversed: bool) -> impl Iterator
- {
+ if is_reversed {
+ let n: Vec = node.children.into_iter().rev().map(|a| a).collect();
+ n.into_iter()
+ } else {
+ node.children.into_iter()
+ }
+}
+
+struct DrawData<'a> {
+ indent: String,
+ percent_bar: String,
+ 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
+ }
+
+ fn generate_bar(&self, node: &Node, level: usize) -> String {
+ let chars_in_bar = self.percent_bar.chars().count();
+ let num_bars = chars_in_bar as f32 * self.display_data.percent_size(node);
+ let mut num_not_my_bar = (chars_in_bar as i32) - num_bars as i32;
+
+ let mut new_bar = "".to_string();
+ let idx = 5 - min(5, max(1, level));
+
+ 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
+ }
+}
+
+fn get_width_of_terminal() -> u16 {
+ // Windows CI runners detect a very low terminal width
+ if let Some((Width(w), Height(_h))) = terminal_size() {
+ max(w, DEFAULT_TERMINAL_WIDTH)
+ } else {
+ DEFAULT_TERMINAL_WIDTH
+ }
}
pub fn draw_it(
@@ -80,44 +138,91 @@ pub fn draw_it(
use_full_path: bool,
is_reversed: bool,
no_colors: bool,
+ no_percents: bool,
root_node: Node,
) {
if !permissions {
eprintln!("Did not have permissions for all directories");
}
- let display_data = DisplayData {
- short_paths: !use_full_path,
- is_reversed,
- colors_on: !no_colors,
+ let longest_string_length = root_node
+ .children
+ .iter()
+ .map(|c| find_longest_dir_name(&c, " ", !use_full_path))
+ .fold(0, max);
+ let terminal_width = get_width_of_terminal() - 16;
+
+ let max_bar_length = if no_percents || longest_string_length >= terminal_width as usize {
+ 0
+ } else {
+ terminal_width as usize - longest_string_length
};
- for c in display_data.get_children_from_node(root_node) {
- let first_tree_chars = display_data.get_first_chars();
- display_node(c, true, first_tree_chars, &display_data)
+ // handle usize error also add do not show fancy output option
+ let bar_text = repeat(BLOCKS[0]).take(max_bar_length).collect::();
+
+ for c in get_children_from_node(root_node, is_reversed) {
+ let display_data = DisplayData {
+ short_paths: !use_full_path,
+ is_reversed,
+ colors_on: !no_colors,
+ base_size: c.size,
+ longest_string_length,
+ };
+ let draw_data = DrawData {
+ indent: "".to_string(),
+ percent_bar: bar_text.clone(),
+ display_data: &display_data,
+ };
+ display_node(c, &draw_data, true, true);
}
}
-fn display_node(node: Node, is_biggest: bool, indent: &str, display_data: &DisplayData) {
- let mut num_siblings = node.children.len() as u64;
- let max_sibling = num_siblings;
- let new_indent = clean_indentation_string(indent);
- let name = node.name.clone();
- let size = node.size;
+// We can probably pass depth instead of indent here.
+// It is ugly to feed in ' ' instead of the actual tree characters but we don't need them yet.
+fn find_longest_dir_name(node: &Node, indent: &str, long_paths: bool) -> usize {
+ let longest = UnicodeWidthStr::width(&*get_printable_name(&node.name, long_paths, indent));
- if !display_data.is_reversed {
- print_this_node(&name, size, is_biggest, display_data, indent);
+ // each none root tree drawing is 2 chars
+ let full_indent: String = indent.to_string() + " ";
+ node.children
+ .iter()
+ .map(|c| find_longest_dir_name(c, &*full_indent, long_paths))
+ .fold(longest, max)
+}
+
+fn display_node(node: Node, draw_data: &DrawData, is_biggest: bool, is_last: bool) {
+ let indent2 = draw_data.get_new_indent(!node.children.is_empty(), is_last);
+ // hacky way of working out how deep we are in the tree
+ let level = ((indent2.chars().count() - 1) / 2) - 1;
+ let bar_text = draw_data.generate_bar(&node, level);
+
+ let to_print = format_string(
+ &node,
+ &*indent2,
+ &*bar_text,
+ is_biggest,
+ draw_data.display_data,
+ );
+
+ if !draw_data.display_data.is_reversed {
+ println!("{}", to_print)
}
- for c in display_data.get_children_from_node(node) {
- num_siblings -= 1;
- let chars = display_data.get_tree_chars(num_siblings, max_sibling, !c.children.is_empty());
- let is_biggest = display_data.is_biggest(num_siblings, max_sibling);
- let full_indent = new_indent.clone() + chars;
- display_node(c, is_biggest, &*full_indent, display_data)
+ let dd = DrawData {
+ indent: clean_indentation_string(&*indent2),
+ percent_bar: bar_text,
+ display_data: draw_data.display_data,
+ };
+ let num_siblings = node.children.len() as u64;
+
+ for (count, c) in get_children_from_node(node, draw_data.display_data.is_reversed).enumerate() {
+ let 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 display_data.is_reversed {
- print_this_node(&name, size, is_biggest, display_data, indent);
+ if draw_data.display_data.is_reversed {
+ println!("{}", to_print)
}
}
@@ -138,30 +243,10 @@ fn clean_indentation_string(s: &str) -> String {
is
}
-fn print_this_node>(
- name: P,
- size: u64,
- is_biggest: bool,
- display_data: &DisplayData,
- indentation: &str,
-) {
- let pretty_size = format!("{:>5}", human_readable_number(size),);
- println!(
- "{}",
- format_string(name, is_biggest, display_data, &*pretty_size, indentation)
- )
-}
-
-pub fn format_string>(
- dir_name: P,
- is_biggest: bool,
- display_data: &DisplayData,
- size: &str,
- indentation: &str,
-) -> String {
+fn get_printable_name>(dir_name: &P, long_paths: bool, indentation: &str) -> String {
let dir_name = dir_name.as_ref();
let printable_name = {
- if display_data.short_paths {
+ if long_paths {
match dir_name.parent() {
Some(prefix) => match dir_name.strip_prefix(prefix) {
Ok(base) => base,
@@ -173,15 +258,44 @@ pub fn format_string>(
dir_name
}
};
+ format!("{} {}", indentation, printable_name.display())
+}
+
+pub fn format_string(
+ node: &Node,
+ indent: &str,
+ percent_bar: &str,
+ is_biggest: bool,
+ display_data: &DisplayData,
+) -> String {
+ let pretty_size = format!("{:>5}", human_readable_number(node.size));
+
+ let percent_size = display_data.percent_size(node);
+ let percent_size_str = format!("{:.0}%", percent_size * 100.0);
+
+ let tree_and_path = get_printable_name(&node.name, display_data.short_paths, &*indent);
+
+ let printable_chars = UnicodeWidthStr::width(&*tree_and_path);
+ let tree_and_path = tree_and_path
+ + &(repeat(" ")
+ .take(display_data.longest_string_length - printable_chars)
+ .collect::());
+
+ let percents = if percent_bar != "" {
+ format!("│{} │ {:>4}", percent_bar, percent_size_str)
+ } else {
+ "".into()
+ };
+
format!(
- "{} {} {}",
+ "{} {}{}",
if is_biggest && display_data.colors_on {
- Fixed(196).paint(size)
+ Fixed(196).paint(pretty_size)
} else {
- Style::new().paint(size)
+ Style::new().paint(pretty_size)
},
- indentation,
- printable_name.display(),
+ tree_and_path,
+ percents,
)
}
diff --git a/src/main.rs b/src/main.rs
index 583fdd8..69d2681 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,16 +1,19 @@
#[macro_use]
extern crate clap;
+extern crate unicode_width;
use self::display::draw_it;
use crate::utils::is_a_parent_of;
use clap::{App, AppSettings, Arg};
+use std::cmp::max;
use std::path::PathBuf;
+use terminal_size::{terminal_size, Height, Width};
use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, trim_deep_ones, Node};
mod display;
mod utils;
-static DEFAULT_NUMBER_OF_LINES: usize = 20;
+static DEFAULT_NUMBER_OF_LINES: usize = 30;
#[cfg(windows)]
fn init_color() {
@@ -20,9 +23,21 @@ fn init_color() {
#[cfg(not(windows))]
fn init_color() {}
+fn get_height_of_terminal() -> usize {
+ // Windows CI runners detect a terminal height of 0
+ if let Some((Width(_w), Height(h))) = terminal_size() {
+ max(h as usize, DEFAULT_NUMBER_OF_LINES) - 10
+ } else {
+ DEFAULT_NUMBER_OF_LINES - 10
+ }
+}
+
fn main() {
init_color();
- let def_num_str = DEFAULT_NUMBER_OF_LINES.to_string();
+
+ let default_height = get_height_of_terminal();
+ let def_num_str = default_height.to_string();
+
let options = App::new("Dust")
.about("Like du but more intuitive")
.version(crate_version!())
@@ -80,7 +95,7 @@ fn main() {
Arg::with_name("reverse")
.short("r")
.long("reverse")
- .help("If applied tree will be printed upside down (biggest lowest)"),
+ .help("If applied tree will be printed upside down (biggest highest)"),
)
.arg(
Arg::with_name("no_colors")
@@ -88,6 +103,12 @@ fn main() {
.long("no_colors")
.help("If applied no colors will be printed (normally largest directories are marked in red"),
)
+ .arg(
+ Arg::with_name("no_bars")
+ .short("b")
+ .long("no_percent_bars")
+ .help("If applied no percent bars or percents will be displayed"),
+ )
.arg(Arg::with_name("inputs").multiple(true))
.get_matches();
@@ -102,7 +123,7 @@ fn main() {
Ok(v) => v,
Err(_) => {
eprintln!("Ignoring bad value for number_of_lines");
- DEFAULT_NUMBER_OF_LINES
+ default_height
}
};
@@ -131,7 +152,7 @@ fn main() {
.map_err(|_| eprintln!("Ignoring bad value for depth"))
.ok()
});
- if options.is_present("depth") && number_of_lines != DEFAULT_NUMBER_OF_LINES {
+ if options.is_present("depth") && number_of_lines != default_height {
eprintln!("Use either -n or -d. Not both");
return;
}
@@ -163,8 +184,9 @@ fn main() {
draw_it(
permissions,
options.is_present("display_full_paths"),
- options.is_present("reverse"),
+ !options.is_present("reverse"),
options.is_present("no_colors"),
+ options.is_present("no_bars"),
tree,
);
}
@@ -200,6 +222,3 @@ fn recursively_build_tree(parent_node: &mut Node, new_node: Node, depth: Option<
parent_node.children.push(new_node);
}
}
-
-#[cfg(test)]
-mod tests;
diff --git a/src/test_dir3/ラウトは難しいです!.japan b/src/test_dir3/ラウトは難しいです!.japan
new file mode 100644
index 0000000..e69de29
diff --git a/src/test_dir3/👩.unicode b/src/test_dir3/👩.unicode
new file mode 100644
index 0000000..e69de29
diff --git a/src/tests.rs b/src/tests.rs
deleted file mode 100644
index 1efd4cd..0000000
--- a/src/tests.rs
+++ /dev/null
@@ -1,412 +0,0 @@
-use super::*;
-
-use crate::display::DisplayData;
-use display::format_string;
-use std::fs::File;
-use std::io::Write;
-use std::panic;
-use std::path::PathBuf;
-use std::process::Command;
-use tempfile::Builder;
-use tempfile::TempDir;
-
-// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
-#[cfg_attr(target_os = "windows", ignore)]
-#[test]
-pub fn test_main() {
- assert_cli::Assert::main_binary()
- .with_args(&["src/test_dir"])
- .stdout()
- .is(main_output(true).as_str())
- .unwrap();
-}
-
-// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
-#[cfg_attr(target_os = "windows", ignore)]
-#[test]
-pub fn test_main_long_paths() {
- assert_cli::Assert::main_binary()
- .with_args(&["-p", "src/test_dir"])
- .stdout()
- .is(main_output(false).as_str())
- .unwrap();
-}
-
-// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
-#[cfg_attr(target_os = "windows", ignore)]
-#[test]
-pub fn test_main_multi_arg() {
- assert_cli::Assert::main_binary()
- .with_args(&["src/test_dir/many/", "src/test_dir/", "src/test_dir"])
- .stdout()
- .is(main_output(true).as_str())
- .unwrap();
-}
-
-#[cfg(target_os = "macos")]
-fn main_output(short_paths: bool) -> String {
- let d = DisplayData {
- short_paths,
- is_reversed: false,
- colors_on: true,
- };
- format!(
- "{}
-{}
-{}
-{}",
- format_string("src/test_dir", true, &d, " 4.0K", "─┬"),
- format_string("src/test_dir/many", true, &d, " 4.0K", " └─┬",),
- format_string("src/test_dir/many/hello_file", true, &d, " 4.0K", " ├──",),
- format_string("src/test_dir/many/a_file", false, &d, " 0B", " └──",),
- )
-}
-
-#[cfg(target_os = "linux")]
-fn main_output(short_paths: bool) -> String {
- let d = DisplayData {
- short_paths,
- is_reversed: false,
- colors_on: true,
- };
- format!(
- "{}
-{}
-{}
-{}",
- format_string("src/test_dir", true, &d, " 12K", "─┬"),
- format_string("src/test_dir/many", true, &d, " 8.0K", " └─┬",),
- format_string("src/test_dir/many/hello_file", true, &d, " 4.0K", " ├──",),
- format_string("src/test_dir/many/a_file", false, &d, " 0B", " └──",),
- )
-}
-
-#[cfg(target_os = "windows")]
-fn main_output(short_paths: bool) -> String {
- let d = DisplayData {
- short_paths,
- is_reversed: false,
- colors_on: true,
- };
- format!(
- "{}
-{}
-{}
-{}",
- format_string("src/test_dir", true, &d, " 6B", "─┬"),
- format_string("src/test_dir\\many", true, &d, " 6B", " └─┬",),
- format_string(
- "src/test_dir\\many\\hello_file",
- true,
- &d,
- " 6B",
- " ├──",
- ),
- format_string("src/test_dir\\many\\a_file", false, &d, " 0B", " └──",),
- )
-}
-
-// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
-#[cfg_attr(target_os = "windows", ignore)]
-#[test]
-pub fn test_no_color_flag() {
- assert_cli::Assert::main_binary()
- .with_args(&["-c", "src/test_dir/"])
- .stdout()
- .is(no_color_flag_output().as_str())
- .unwrap();
-}
-
-#[cfg(target_os = "macos")]
-fn no_color_flag_output() -> String {
- "
- 4.0K ─┬ test_dir
- 4.0K └─┬ many
- 4.0K ├── hello_file
- 0B └── a_file
- "
- .to_string()
-}
-
-#[cfg(target_os = "linux")]
-fn no_color_flag_output() -> String {
- "
- 12K ─┬ test_dir
- 8.0K └─┬ many
- 4.0K ├── hello_file
- 0B └── a_file
- "
- .to_string()
-}
-
-#[cfg(target_os = "windows")]
-fn no_color_flag_output() -> String {
- "
- 6B ─┬ test_dir
- 6B └─┬ many
- 6B ├── hello_file
- 0B └── a_file
- "
- .to_string()
-}
-
-// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
-#[cfg_attr(target_os = "windows", ignore)]
-#[test]
-pub fn test_apparent_size() {
- let d = DisplayData {
- short_paths: true,
- is_reversed: false,
- colors_on: true,
- };
- let r = format!(
- "{}",
- format_string("src/test_dir/many/hello_file", true, &d, " 6B", " ├──",),
- );
-
- assert_cli::Assert::main_binary()
- .with_args(&["-s", "src/test_dir"])
- .stdout()
- .contains(r.as_str())
- .unwrap();
-}
-
-#[test]
-pub fn test_reverse_flag() {
- // variable names the same length make the output easier to read
- let a = " ┌── a_file";
- let b = " ├── hello_file";
- let c = " ┌─┴ many";
- let d = " ─┴ test_dir";
-
- assert_cli::Assert::main_binary()
- .with_args(&["-r", "src/test_dir"])
- .stdout()
- .contains(a)
- .stdout()
- .contains(b)
- .stdout()
- .contains(c)
- .stdout()
- .contains(d)
- .unwrap();
-}
-
-#[test]
-pub fn test_d_flag_works() {
- // We should see the top level directory but not the sub dirs / files:
- assert_cli::Assert::main_binary()
- .with_args(&["-d", "1", "-s", "src/test_dir"])
- .stdout()
- .doesnt_contain("hello_file")
- .unwrap();
-}
-
-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
-}
-
-// fix! [rivy; 2020-01-22] possible on "windows"?; `ln` is not usually an available command; creation of symbolic links requires special enhanced permissions
-// ... ref: @@
-#[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 a = format!(" ─┬ {}", dir_s);
- let b = format!(" ├── {}", file_path_s);
- let c = format!(" └── {}", link_name_s);
-
- assert_cli::Assert::main_binary()
- .with_args(&["-p", &dir_s])
- .stdout()
- .contains(a.as_str())
- .stdout()
- .contains(b.as_str())
- .stdout()
- .contains(c.as_str())
- .unwrap();
-}
-
-// Hard links are ignored as the inode is the same as the file
-// fix! [rivy; 2020-01-22] may fail on "usual" windows hosts as `ln` is not usually an available command
-#[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 a = format!(" ─┬ {}", dir_s);
- let b = format!(" └── {}", link_name_s);
- let b2 = format!(" └── {}", file_path_s);
-
- // Because this is a hard link the file and hard link look identical. Therefore
- // we cannot guarantee which version will appear first.
- let result = panic::catch_unwind(|| {
- assert_cli::Assert::main_binary()
- .with_args(&["-p", dir_s])
- .stdout()
- .contains(a.as_str())
- .stdout()
- .contains(b.as_str())
- .unwrap();
- });
- if result.is_err() {
- assert_cli::Assert::main_binary()
- .with_args(&["-p", dir_s])
- .stdout()
- .contains(a.as_str())
- .stdout()
- .contains(b2.as_str())
- .unwrap();
- }
-}
-
-// Check we don't recurse down an infinite symlink tree
-// fix! [rivy; 2020-01-22] possible on "windows"?; `ln` is not usually an available command; creation of symbolic links requires special enhanced permissions
-// ... ref: @@
-#[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);
-
- assert_cli::Assert::main_binary()
- .with_args(&["-p", dir_s])
- .stdout()
- .contains(a.as_str())
- .stdout()
- .contains(b.as_str())
- .unwrap();
-}
-
-// Check against directories and files whos names are substrings of each other
-// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
-#[cfg_attr(target_os = "windows", ignore)]
-#[test]
-pub fn test_substring_of_names() {
- assert_cli::Assert::main_binary()
- .with_args(&["-c", "src/test_dir2"])
- .stdout()
- .is(no_substring_of_names_output().as_str())
- .unwrap();
-}
-
-#[cfg(target_os = "linux")]
-fn no_substring_of_names_output() -> String {
- "
- 24K ─┬ test_dir2
- 8.0K ├─┬ dir
- 4.0K │ └── hello
- 8.0K ├─┬ dir_substring
- 4.0K │ └── hello
- 4.0K └── dir_name_clash
- "
- .into()
-}
-
-#[cfg(target_os = "macos")]
-fn no_substring_of_names_output() -> String {
- "
- 12K ─┬ test_dir2
- 4.0K ├─┬ dir
- 4.0K │ └── hello
- 4.0K ├── dir_name_clash
- 4.0K └─┬ dir_substring
- 4.0K └── hello
- "
- .into()
-}
-
-#[cfg(target_os = "windows")]
-fn no_substring_of_names_output() -> String {
- "
- 16B ─┬ test_dir2
- 6B ├─┬ dir_substring
- 6B │ └── hello
- 5B ├─┬ dir
- 5B │ └── hello
- 5B └── dir_name_clash
- "
- .into()
-}
-
-// Check against directories and files whos names are substrings of each other
-#[test]
-pub fn test_ignore_dir() {
- assert_cli::Assert::main_binary()
- .with_args(&["-c", "-X", "dir_substring", "src/test_dir2"])
- .stdout()
- .is(ignore_dir_output().as_str())
- .unwrap();
-}
-
-#[cfg(target_os = "linux")]
-fn ignore_dir_output() -> String {
- "
- 16K ─┬ test_dir2
- 8.0K ├─┬ dir
- 4.0K │ └── hello
- 4.0K └── dir_name_clash
- "
- .into()
-}
-
-#[cfg(target_os = "macos")]
-fn ignore_dir_output() -> String {
- "
- 8.0K ─┬ test_dir2
- 4.0K ├─┬ dir
- 4.0K │ └── hello
- 4.0K └── dir_name_clash
- "
- .into()
-}
-
-#[cfg(target_os = "windows")]
-fn ignore_dir_output() -> String {
- "
- 10B ─┬ test_dir2
- 5B ├─┬ dir
- 5B │ └── hello
- 5B └── dir_name_clash
- "
- .into()
-}
diff --git a/tests/tests.rs b/tests/tests.rs
new file mode 100644
index 0000000..4087fce
--- /dev/null
+++ b/tests/tests.rs
@@ -0,0 +1,259 @@
+mod tests_symlinks;
+
+// File sizes differ on both platform and on the format of the disk.
+// We can at least test the file names are there
+#[test]
+pub fn test_basic_output() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["src/test_dir/"])
+ .stdout()
+ .contains(" ┌─┴ test_dir ")
+ .stdout()
+ .contains(" ┌─┴ many ")
+ .stdout()
+ .contains(" ├── hello_file")
+ .stdout()
+ .contains(" ┌── a_file ")
+ .unwrap();
+}
+
+// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
+#[cfg_attr(target_os = "windows", ignore)]
+#[test]
+pub fn test_main_basic() {
+ // -c is no color mode - This makes testing much simpler
+ assert_cli::Assert::main_binary()
+ .with_args(&["-c", "src/test_dir"])
+ .stdout()
+ .is(main_output().as_str())
+ .unwrap();
+}
+
+// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
+#[cfg_attr(target_os = "windows", ignore)]
+#[test]
+pub fn test_main_multi_arg() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-c", "src/test_dir/many/", "src/test_dir/", "src/test_dir"])
+ .stdout()
+ .is(main_output().as_str())
+ .unwrap();
+}
+
+#[cfg(target_os = "macos")]
+fn main_output() -> String {
+ r#"
+ 0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
+ 4.0K ├── hello_file│██████████████████████████████████████████████ │ 100%
+ 4.0K ┌─┴ many │██████████████████████████████████████████████ │ 100%
+ 4.0K ┌─┴ test_dir │██████████████████████████████████████████████ │ 100%
+ "#
+ .to_string()
+}
+
+#[cfg(target_os = "linux")]
+fn main_output() -> String {
+ r#"
+ 0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
+ 4.0K ├── hello_file│ ░░░░░░░░░░░░░░░████████████████ │ 33%
+ 8.0K ┌─┴ many │ ███████████████████████████████ │ 67%
+ 12K ┌─┴ test_dir │██████████████████████████████████████████████ │ 100%
+ "#
+ .to_string()
+}
+
+#[cfg(target_os = "windows")]
+fn main_output() -> String {
+ "PRs welcome".to_string()
+}
+
+// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
+#[cfg_attr(target_os = "windows", ignore)]
+#[test]
+pub fn test_main_long_paths() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-c", "-p", "src/test_dir"])
+ .stdout()
+ .is(main_output_long_paths().as_str())
+ .unwrap();
+}
+
+#[cfg(target_os = "macos")]
+fn main_output_long_paths() -> String {
+ r#"
+ 0B ┌── src/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
+ 4.0K ├── src/test_dir/many/hello_file│████████████████████████████ │ 100%
+ 4.0K ┌─┴ src/test_dir/many │████████████████████████████ │ 100%
+ 4.0K ┌─┴ src/test_dir │████████████████████████████ │ 100%
+ "#
+ .to_string()
+}
+
+#[cfg(target_os = "linux")]
+fn main_output_long_paths() -> String {
+ r#"
+ 0B ┌── src/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░█ │ 0%
+ 4.0K ├── src/test_dir/many/hello_file│ ░░░░░░░░░██████████ │ 33%
+ 8.0K ┌─┴ src/test_dir/many │ ███████████████████ │ 67%
+ 12K ┌─┴ src/test_dir │████████████████████████████ │ 100%
+ "#
+ .to_string()
+}
+
+#[cfg(target_os = "windows")]
+fn main_output_long_paths() -> String {
+ "PRs welcome".to_string()
+}
+
+// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
+#[cfg_attr(target_os = "windows", ignore)]
+#[test]
+pub fn test_apparent_size() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-c", "-s", "src/test_dir"])
+ .stdout()
+ .is(output_apparent_size().as_str())
+ .unwrap();
+}
+
+#[cfg(target_os = "linux")]
+fn output_apparent_size() -> String {
+ r#"
+ 0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
+ 6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
+ 4.0K ┌─┴ many │ ████████████████████████ │ 50%
+ 8.0K ┌─┴ test_dir │██████████████████████████████████████████████ │ 100%
+ "#
+ .to_string()
+}
+
+#[cfg(target_os = "macos")]
+fn output_apparent_size() -> String {
+ r#"
+ 0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
+ 6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░░██ │ 3%
+ 134B ┌─┴ many │ ███████████████████████████ │ 58%
+ 230B ┌─┴ test_dir │██████████████████████████████████████████████ │ 100%
+ "#
+ .to_string()
+}
+
+#[cfg(target_os = "windows")]
+fn output_apparent_size() -> String {
+ "".to_string()
+}
+
+#[test]
+pub fn test_reverse_flag() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-c", "-r", "src/test_dir/"])
+ .stdout()
+ .contains(" └─┬ test_dir ")
+ .stdout()
+ .contains(" └─┬ many ")
+ .stdout()
+ .contains(" ├── hello_file")
+ .stdout()
+ .contains(" └── a_file ")
+ .unwrap();
+}
+
+#[test]
+pub fn test_d_flag_works() {
+ // We should see the top level directory but not the sub dirs / files:
+ assert_cli::Assert::main_binary()
+ .with_args(&["-d", "1", "-s", "src/test_dir"])
+ .stdout()
+ .doesnt_contain("hello_file")
+ .unwrap();
+}
+
+// Check against directories and files whos names are substrings of each other
+// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
+#[cfg_attr(target_os = "windows", ignore)]
+#[test]
+pub fn test_substring_of_names() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-c", "src/test_dir2"])
+ .stdout()
+ .is(no_substring_of_names_output().as_str())
+ .unwrap();
+}
+
+#[cfg(target_os = "linux")]
+fn no_substring_of_names_output() -> String {
+ "
+ 4.0K ┌── dir_name_clash│ ████████ │ 17%
+ 4.0K │ ┌── hello │ ░░░░░░░████████ │ 17%
+ 8.0K ├─┴ dir_substring │ ███████████████ │ 33%
+ 4.0K │ ┌── hello │ ░░░░░░░████████ │ 17%
+ 8.0K ├─┴ dir │ ███████████████ │ 33%
+ 24K ┌─┴ test_dir2 │████████████████████████████████████████████ │ 100%
+ "
+ .into()
+}
+
+#[cfg(target_os = "macos")]
+fn no_substring_of_names_output() -> String {
+ "
+ 4.0K ┌── hello │ ███████████████ │ 33%
+ 4.0K ┌─┴ dir_substring │ ███████████████ │ 33%
+ 4.0K ├── dir_name_clash│ ███████████████ │ 33%
+ 4.0K │ ┌── hello │ ███████████████ │ 33%
+ 4.0K ├─┴ dir │ ███████████████ │ 33%
+ 12K ┌─┴ test_dir2 │████████████████████████████████████████████ │ 100%
+ "
+ .into()
+}
+
+#[cfg(target_os = "windows")]
+fn no_substring_of_names_output() -> String {
+ "PRs".into()
+}
+
+// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
+#[cfg_attr(target_os = "windows", ignore)]
+#[test]
+pub fn test_unicode_directories() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-c", "src/test_dir3"])
+ .stdout()
+ .is(unicode_dir().as_str())
+ .unwrap();
+}
+
+#[cfg(target_os = "linux")]
+fn unicode_dir() -> String {
+ // The way unicode & asian characters are rendered on the terminal should make this line up
+ "
+ 0B ┌── 👩.unicode │ █ │ 0%
+ 0B ├── ラウトは難しいです!.japan│ █ │ 0%
+ 4.0K ┌─┴ test_dir3 │████████████████████████████████ │ 100%
+ "
+ .into()
+}
+
+#[cfg(target_os = "macos")]
+fn unicode_dir() -> String {
+ "
+ 0B ┌── 👩.unicode │ █ │ 0%
+ 0B ├── ラウトは難しいです!.japan│ █ │ 0%
+ 0B ┌─┴ test_dir3 │ █ │ 0%
+ "
+ .into()
+}
+
+#[cfg(target_os = "windows")]
+fn unicode_dir() -> String {
+ "".into()
+}
+
+// Check against directories and files whos names are substrings of each other
+#[test]
+pub fn test_ignore_dir() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-c", "-X", "dir_substring", "src/test_dir2"])
+ .stdout()
+ .doesnt_contain("dir_substring")
+ .unwrap();
+}
diff --git a/tests/tests_symlinks.rs b/tests/tests_symlinks.rs
new file mode 100644
index 0000000..6733323
--- /dev/null
+++ b/tests/tests_symlinks.rs
@@ -0,0 +1,119 @@
+use std::fs::File;
+use std::io::Write;
+use std::panic;
+use std::path::PathBuf;
+use std::process::Command;
+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);
+
+ assert_cli::Assert::main_binary()
+ .with_args(&["-p", &dir_s])
+ .stdout()
+ .contains(a.as_str())
+ .stdout()
+ .contains(b.as_str())
+ .stdout()
+ .contains(c.as_str())
+ .unwrap();
+}
+
+#[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 a = format!("─┴ {}", dir_s);
+ let b = format!(" ┌── {}", link_name_s);
+ let b2 = format!(" ┌── {}", file_path_s);
+
+ // Because this is a hard link the file and hard link look identical. Therefore
+ // we cannot guarantee which version will appear first.
+ let result = panic::catch_unwind(|| {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-p", dir_s])
+ .stdout()
+ .contains(a.as_str())
+ .stdout()
+ .contains(b.as_str())
+ .unwrap();
+ });
+ if result.is_err() {
+ assert_cli::Assert::main_binary()
+ .with_args(&["-p", dir_s])
+ .stdout()
+ .contains(a.as_str())
+ .stdout()
+ .contains(b2.as_str())
+ .unwrap();
+ }
+}
+
+#[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);
+
+ assert_cli::Assert::main_binary()
+ .with_args(&["-r", "-p", dir_s])
+ .stdout()
+ .contains(a.as_str())
+ .stdout()
+ .contains(b.as_str())
+ .unwrap();
+}