mirror of
https://github.com/bootandy/dust.git
synced 2026-06-08 11:29:05 +03:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36fd75b9ee | |||
| 51a0ccb1ed | |||
| 68b0dd5562 | |||
| 360143ee91 |
@@ -82,11 +82,11 @@ jobs:
|
|||||||
job:
|
job:
|
||||||
# { os, target, cargo-options, features, use-cross, toolchain }
|
# { os, target, cargo-options, features, use-cross, toolchain }
|
||||||
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , use-cross: use-cross }
|
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , use-cross: use-cross }
|
||||||
- { os: ubuntu-20.04 , target: i686-unknown-linux-gnu , use-cross: use-cross }
|
- { os: ubuntu-18.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-18.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-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: macos-latest , target: x86_64-apple-darwin }
|
||||||
- { os: windows-latest , target: i686-pc-windows-gnu }
|
- { os: windows-latest , target: i686-pc-windows-gnu }
|
||||||
- { os: windows-latest , target: i686-pc-windows-msvc }
|
- { os: windows-latest , target: i686-pc-windows-msvc }
|
||||||
@@ -205,18 +205,6 @@ jobs:
|
|||||||
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
|
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
|
||||||
command: build
|
command: build
|
||||||
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
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
|
- name: Test
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@@ -228,12 +216,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
|
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
|
||||||
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
|
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
|
- name: Package
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -257,12 +239,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
|
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
|
||||||
target/${{ matrix.job.target }}/debian
|
|
||||||
${{ env.PROJECT_NAME }}-${{ matrix.job.target }}.deb
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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?
|
## fix! [rivy; 2020-22-01] `cargo tarpaulin` is unable to test this repo at the moment; alternate recipe or another testing framework?
|
||||||
# coverage:
|
# coverage:
|
||||||
# name: Code Coverage
|
# name: Code Coverage
|
||||||
|
|||||||
Generated
+147
-148
@@ -1,12 +1,10 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.18"
|
version = "0.7.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -31,11 +29,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assert_cmd"
|
name = "assert_cmd"
|
||||||
version = "1.0.8"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe"
|
checksum = "3dc1679af9a1ab4bea16f228b05d18f8363f8327b1fa8db00d2760cfafc6b61e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bstr",
|
|
||||||
"doc-comment",
|
"doc-comment",
|
||||||
"predicates",
|
"predicates",
|
||||||
"predicates-core",
|
"predicates-core",
|
||||||
@@ -62,21 +59,25 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "0.2.16"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
|
checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -93,7 +94,6 @@ dependencies = [
|
|||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"strsim",
|
"strsim",
|
||||||
"term_size",
|
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"vec_map",
|
"vec_map",
|
||||||
@@ -101,53 +101,41 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.1"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"crossbeam-utils 0.7.2",
|
||||||
"crossbeam-utils",
|
"maybe-uninit",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-deque"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"crossbeam-epoch",
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-epoch"
|
|
||||||
version = "0.9.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"crossbeam-utils",
|
|
||||||
"lazy_static",
|
|
||||||
"memoffset",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.5"
|
version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"autocfg",
|
||||||
|
"cfg-if 0.1.10",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "difflib"
|
name = "crossbeam-utils"
|
||||||
version = "0.4.0"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "difference"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "doc-comment"
|
name = "doc-comment"
|
||||||
@@ -157,55 +145,79 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "du-dust"
|
name = "du-dust"
|
||||||
version = "0.7.1"
|
version = "0.5.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.12.1",
|
"ansi_term 0.12.1",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"clap",
|
"clap",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"ignore",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
"rayon",
|
"num_cpus",
|
||||||
"regex",
|
|
||||||
"stfu8",
|
"stfu8",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"terminal_size",
|
"terminal_size",
|
||||||
"thousands",
|
"thousands",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
"walkdir",
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "fnv"
|
||||||
version = "1.6.1"
|
version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.3"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
checksum = "4060f4657be78b8e766215b02b18a2e862d83745545de804638e2b545e81aee6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "globset"
|
||||||
version = "0.1.19"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"bstr",
|
||||||
|
"fnv",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "ignore"
|
||||||
version = "0.10.1"
|
version = "0.4.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
|
checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"crossbeam-utils 0.8.1",
|
||||||
|
"globset",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"regex",
|
||||||
|
"same-file",
|
||||||
|
"thread_local",
|
||||||
|
"walkdir",
|
||||||
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -216,9 +228,18 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.101"
|
version = "0.2.82"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lscolors"
|
name = "lscolors"
|
||||||
@@ -230,19 +251,16 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "maybe-uninit"
|
||||||
version = "2.4.1"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memchr"
|
||||||
version = "0.6.4"
|
version = "2.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
|
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
@@ -262,26 +280,25 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "predicates"
|
name = "predicates"
|
||||||
version = "2.0.2"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c143348f141cc87aab5b950021bac6145d0e5ae754b0591de23244cee42c9308"
|
checksum = "73dd9b7b200044694dfede9edf907c1ca19630908443e9447e624993700c6932"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"difflib",
|
"difference",
|
||||||
"itertools",
|
|
||||||
"predicates-core",
|
"predicates-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "predicates-core"
|
name = "predicates-core"
|
||||||
version = "1.0.2"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
|
checksum = "fb3dbeaaf793584e29c58c7e3a82bbb3c7c06b63cea68d13b0e3cddc124104dc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "predicates-tree"
|
name = "predicates-tree"
|
||||||
version = "1.0.3"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7dd0fd014130206c9352efbdc92be592751b2b9274dff685348341082c6ea3d"
|
checksum = "aee95d988ee893cb35c06b148c80ed2cd52c8eea927f50ba7a0be1a786aeab73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"predicates-core",
|
"predicates-core",
|
||||||
"treeline",
|
"treeline",
|
||||||
@@ -289,9 +306,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.4"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
checksum = "18519b42a40024d661e1714153e9ad0c3de27cd495760ceb09710920f1098b1e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
@@ -301,9 +318,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.3.1"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
@@ -311,78 +328,48 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.6.3"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_hc"
|
name = "rand_hc"
|
||||||
version = "0.3.1"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rayon"
|
|
||||||
version = "1.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"crossbeam-deque",
|
|
||||||
"either",
|
|
||||||
"rayon-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rayon-core"
|
|
||||||
version = "1.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-channel",
|
|
||||||
"crossbeam-deque",
|
|
||||||
"crossbeam-utils",
|
|
||||||
"lazy_static",
|
|
||||||
"num_cpus",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.10"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
|
"thread_local",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.25"
|
version = "0.6.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
@@ -394,10 +381,13 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "same-file"
|
||||||
version = "1.1.0"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stfu8"
|
name = "stfu8"
|
||||||
@@ -421,7 +411,7 @@ version = "3.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"rand",
|
"rand",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
@@ -429,21 +419,11 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "term_size"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "terminal_size"
|
name = "terminal_size"
|
||||||
version = "0.1.17"
|
version = "0.1.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi",
|
||||||
@@ -455,7 +435,6 @@ version = "0.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"term_size",
|
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -465,6 +444,15 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
|
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "treeline"
|
name = "treeline"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -493,10 +481,21 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "walkdir"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93c6c3420963c5c64bca373b25e77acb562081b9bb4dd5bb864187742186cea9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
|
|||||||
+9
-20
@@ -1,10 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "du-dust"
|
name = "du-dust"
|
||||||
description = "A more intuitive version of du"
|
description = "A more intuitive version of du"
|
||||||
version = "0.7.1"
|
version = "0.5.4"
|
||||||
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
|
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
|
||||||
|
|
||||||
documentation = "https://github.com/bootandy/dust"
|
documentation = "https://github.com/bootandy/dust"
|
||||||
homepage = "https://github.com/bootandy/dust"
|
homepage = "https://github.com/bootandy/dust"
|
||||||
@@ -15,7 +14,7 @@ categories = ["command-line-utilities"]
|
|||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "https://travis-ci.org/bootandy/dust" }
|
travis-ci = {repository = "https://travis-ci.org/bootandy/dust"}
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "dust"
|
name = "dust"
|
||||||
@@ -23,35 +22,25 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ansi_term = "0.12"
|
ansi_term = "0.12"
|
||||||
clap = { version = "=2.33", features = ["wrap_help"] }
|
clap = "=2.33"
|
||||||
lscolors = "0.7"
|
lscolors = "0.7"
|
||||||
|
num_cpus = "1"
|
||||||
terminal_size = "0.1"
|
terminal_size = "0.1"
|
||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
rayon="1"
|
ignore="0.4"
|
||||||
|
crossbeam-channel = "0.4"
|
||||||
|
walkdir="2.3"
|
||||||
thousands = "0.2"
|
thousands = "0.2"
|
||||||
stfu8 = "0.2"
|
stfu8 = "0.2"
|
||||||
regex = "1"
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi-util = "0.1"
|
winapi-util = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "1"
|
assert_cmd ="1"
|
||||||
tempfile = "=3"
|
tempfile = "=3"
|
||||||
|
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "integration"
|
name = "integration"
|
||||||
path = "tests/tests.rs"
|
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'.
|
|
||||||
"""
|
|
||||||
|
|||||||
@@ -38,27 +38,20 @@ 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.
|
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
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: dust
|
Usage: dust
|
||||||
Usage: dust <dir>
|
Usage: dust <dir>
|
||||||
Usage: dust <dir> <another_dir> <and_more>
|
Usage: dust <dir> <another_dir> <and_more>
|
||||||
Usage: dust -p (full-path - Show fullpath of the subdirectories)
|
Usage: dust -p <dir> (full-path - does not shorten the path 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 -s <dir> (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 -n 30 <dir> (shows 30 directories instead of the default)
|
||||||
Usage: dust -d 3 (shows 3 levels of subdirectories)
|
Usage: dust -d 3 <dir> (shows 3 levels of subdirectories)
|
||||||
Usage: dust -r (reverse order of output)
|
Usage: dust -r <dir> (reverse order of output, with root at the lowest)
|
||||||
Usage: dust -X ignore (ignore all files and directories with the name 'ignore')
|
Usage: dust -x <dir> (only show directories on the same filesystem)
|
||||||
Usage: dust -x (only show directories on the same filesystem)
|
Usage: dust -X ignore <dir> (ignore all files and directories with the name 'ignore')
|
||||||
Usage: dust -b (do not show percentages or draw ASCII bars)
|
Usage: dust -b <dir> (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)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -66,9 +59,6 @@ Usage: dust -e regex Only include files matching this regex (eg dust -e "\.png$"
|
|||||||
|
|
||||||
* [NCDU](https://dev.yorhel.nl/ncdu)
|
* [NCDU](https://dev.yorhel.nl/ncdu)
|
||||||
* [dutree](https://github.com/nachoparker/dutree)
|
* [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
|
* 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.
|
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
@@ -1,6 +1,6 @@
|
|||||||
# ----------- To do a release ---------
|
# ----------- To do a release ---------
|
||||||
# edit version in cargo.toml
|
# edit version in cargo.toml
|
||||||
# tag a commit and push (increment version in Cargo.toml first):
|
# tag a commit and push (increment version first):
|
||||||
# git tag v0.4.5
|
# git tag v0.4.5
|
||||||
# git push origin v0.4.5
|
# git push origin v0.4.5
|
||||||
|
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
use std::fs;
|
|
||||||
|
|
||||||
use crate::node::Node;
|
|
||||||
use crate::utils::is_filtered_out_due_to_invert_regex;
|
|
||||||
use crate::utils::is_filtered_out_due_to_regex;
|
|
||||||
use rayon::iter::ParallelBridge;
|
|
||||||
use rayon::prelude::ParallelIterator;
|
|
||||||
use regex::Regex;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use std::sync::atomic;
|
|
||||||
use std::sync::atomic::AtomicBool;
|
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::node::build_node;
|
|
||||||
use std::fs::DirEntry;
|
|
||||||
|
|
||||||
use crate::platform::get_metadata;
|
|
||||||
|
|
||||||
pub struct WalkData {
|
|
||||||
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, &walk_data);
|
|
||||||
match n {
|
|
||||||
Some(n) => {
|
|
||||||
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
|
|
||||||
clean_inodes(n, &mut inodes, walk_data.use_apparent_size)
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
(top_level_nodes, permissions_flag.into_inner())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove files which have the same inode, we don't want to double count them.
|
|
||||||
fn clean_inodes(
|
|
||||||
x: Node,
|
|
||||||
inodes: &mut HashSet<(u64, u64)>,
|
|
||||||
use_apparent_size: bool,
|
|
||||||
) -> Option<Node> {
|
|
||||||
if !use_apparent_size {
|
|
||||||
if let Some(id) = x.inode_device {
|
|
||||||
if inodes.contains(&id) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
inodes.insert(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_children: Vec<_> = x
|
|
||||||
.children
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|c| clean_inodes(c, inodes, use_apparent_size))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
return Some(Node {
|
|
||||||
name: x.name,
|
|
||||||
size: x.size + new_children.iter().map(|c| c.size).sum::<u64>(),
|
|
||||||
children: new_children,
|
|
||||||
inode_device: x.inode_device,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
|
|
||||||
let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.');
|
|
||||||
let is_ignored_path = walk_data.ignore_directories.contains(&entry.path());
|
|
||||||
|
|
||||||
if !walk_data.allowed_filesystems.is_empty() {
|
|
||||||
let size_inode_device = get_metadata(&entry.path(), false);
|
|
||||||
|
|
||||||
if let Some((_size, Some((_id, dev)))) = size_inode_device {
|
|
||||||
if !walk_data.allowed_filesystems.contains(&dev) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keeping `walk_data.filter_regex.is_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, walk_data: &WalkData) -> Option<Node> {
|
|
||||||
let mut children = vec![];
|
|
||||||
|
|
||||||
if let Ok(entries) = fs::read_dir(dir.clone()) {
|
|
||||||
children = entries
|
|
||||||
.into_iter()
|
|
||||||
.par_bridge()
|
|
||||||
.filter_map(|entry| {
|
|
||||||
if let Ok(ref entry) = entry {
|
|
||||||
// uncommenting the below line gives simpler code but
|
|
||||||
// rayon doesn't parallelise as well giving a 3X performance drop
|
|
||||||
// hence we unravel the recursion a bit
|
|
||||||
|
|
||||||
// return walk(entry.path(), permissions_flag, ignore_directories, allowed_filesystems, use_apparent_size, by_filecount, ignore_hidden);
|
|
||||||
|
|
||||||
if !ignore_file(entry, walk_data) {
|
|
||||||
if let Ok(data) = entry.file_type() {
|
|
||||||
if data.is_dir() && !data.is_symlink() {
|
|
||||||
return walk(entry.path(), permissions_flag, walk_data);
|
|
||||||
}
|
|
||||||
return build_node(
|
|
||||||
entry.path(),
|
|
||||||
vec![],
|
|
||||||
&walk_data.filter_regex,
|
|
||||||
&walk_data.invert_filter_regex,
|
|
||||||
walk_data.use_apparent_size,
|
|
||||||
data.is_symlink(),
|
|
||||||
data.is_file(),
|
|
||||||
walk_data.by_filecount,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
permissions_flag.store(true, atomic::Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
} else {
|
|
||||||
permissions_flag.store(true, atomic::Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
build_node(
|
|
||||||
dir,
|
|
||||||
children,
|
|
||||||
&walk_data.filter_regex,
|
|
||||||
&walk_data.invert_filter_regex,
|
|
||||||
walk_data.use_apparent_size,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
walk_data.by_filecount,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod tests {
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn create_node() -> Node {
|
|
||||||
Node {
|
|
||||||
name: PathBuf::new(),
|
|
||||||
size: 10,
|
|
||||||
children: vec![],
|
|
||||||
inode_device: Some((5, 6)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_should_ignore_file() {
|
|
||||||
let mut inodes = HashSet::new();
|
|
||||||
let n = create_node();
|
|
||||||
|
|
||||||
// First time we insert the node
|
|
||||||
assert!(clean_inodes(n.clone(), &mut inodes, false) == Some(n.clone()));
|
|
||||||
|
|
||||||
// Second time is a duplicate - we ignore it
|
|
||||||
assert!(clean_inodes(n.clone(), &mut inodes, false) == None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_should_not_ignore_files_if_using_apparent_size() {
|
|
||||||
let mut inodes = HashSet::new();
|
|
||||||
let n = create_node();
|
|
||||||
|
|
||||||
// If using apparent size we include Nodes, even if duplicate inodes
|
|
||||||
assert!(clean_inodes(n.clone(), &mut inodes, true) == Some(n.clone()));
|
|
||||||
assert!(clean_inodes(n.clone(), &mut inodes, true) == Some(n.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+34
-38
@@ -1,6 +1,6 @@
|
|||||||
extern crate ansi_term;
|
extern crate ansi_term;
|
||||||
|
|
||||||
use crate::display_node::DisplayNode;
|
use crate::utils::{Errors, Node};
|
||||||
|
|
||||||
use self::ansi_term::Colour::Red;
|
use self::ansi_term::Colour::Red;
|
||||||
use lscolors::{LsColors, Style};
|
use lscolors::{LsColors, Style};
|
||||||
@@ -60,7 +60,7 @@ impl DisplayData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn percent_size(&self, node: &DisplayNode) -> f32 {
|
fn percent_size(&self, node: &Node) -> f32 {
|
||||||
let result = node.size as f32 / self.base_size as f32;
|
let result = node.size as f32 / self.base_size as f32;
|
||||||
if result.is_normal() {
|
if result.is_normal() {
|
||||||
result
|
result
|
||||||
@@ -83,7 +83,7 @@ impl DrawData<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: can we test this?
|
// TODO: can we test this?
|
||||||
fn generate_bar(&self, node: &DisplayNode, level: usize) -> String {
|
fn generate_bar(&self, node: &Node, level: usize) -> String {
|
||||||
let chars_in_bar = self.percent_bar.chars().count();
|
let chars_in_bar = self.percent_bar.chars().count();
|
||||||
let num_bars = chars_in_bar as f32 * self.display_data.percent_size(node);
|
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 num_not_my_bar = (chars_in_bar as i32) - num_bars as i32;
|
||||||
@@ -107,21 +107,23 @@ impl DrawData<'_> {
|
|||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn draw_it(
|
pub fn draw_it(
|
||||||
|
errors: Errors,
|
||||||
use_full_path: bool,
|
use_full_path: bool,
|
||||||
is_reversed: bool,
|
is_reversed: bool,
|
||||||
no_colors: bool,
|
no_colors: bool,
|
||||||
no_percents: bool,
|
no_percents: bool,
|
||||||
terminal_width: usize,
|
terminal_width: usize,
|
||||||
by_filecount: bool,
|
by_filecount: bool,
|
||||||
option_root_node: Option<DisplayNode>,
|
root_node: Node,
|
||||||
) {
|
) {
|
||||||
if option_root_node.is_none() {
|
if errors.permissions {
|
||||||
return;
|
eprintln!("Did not have permissions for all directories");
|
||||||
|
}
|
||||||
|
if errors.not_found {
|
||||||
|
eprintln!("Not all directories were found");
|
||||||
}
|
}
|
||||||
let root_node = option_root_node.unwrap();
|
|
||||||
|
|
||||||
let num_chars_needed_on_left_most = if by_filecount {
|
let num_chars_needed_on_left_most = if by_filecount {
|
||||||
let max_size = root_node.size;
|
let max_size = root_node.children.iter().map(|n| n.size).fold(0, max);
|
||||||
max_size.separate_with_commas().chars().count()
|
max_size.separate_with_commas().chars().count()
|
||||||
} else {
|
} else {
|
||||||
5 // Under normal usage we need 5 chars to display the size of a directory
|
5 // Under normal usage we need 5 chars to display the size of a directory
|
||||||
@@ -129,8 +131,11 @@ pub fn draw_it(
|
|||||||
|
|
||||||
let terminal_width = terminal_width - 9 - num_chars_needed_on_left_most;
|
let terminal_width = terminal_width - 9 - num_chars_needed_on_left_most;
|
||||||
let num_indent_chars = 3;
|
let num_indent_chars = 3;
|
||||||
let longest_string_length =
|
let longest_string_length = root_node
|
||||||
find_longest_dir_name(&root_node, num_indent_chars, terminal_width, !use_full_path);
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|c| find_longest_dir_name(&c, num_indent_chars, terminal_width, !use_full_path))
|
||||||
|
.fold(0, max);
|
||||||
|
|
||||||
let max_bar_length = if no_percents || longest_string_length >= terminal_width as usize {
|
let max_bar_length = if no_percents || longest_string_length >= terminal_width as usize {
|
||||||
0
|
0
|
||||||
@@ -140,30 +145,27 @@ pub fn draw_it(
|
|||||||
|
|
||||||
let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect::<String>();
|
let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect::<String>();
|
||||||
|
|
||||||
|
for c in root_node.get_children_from_node(is_reversed) {
|
||||||
let display_data = DisplayData {
|
let display_data = DisplayData {
|
||||||
short_paths: !use_full_path,
|
short_paths: !use_full_path,
|
||||||
is_reversed,
|
is_reversed,
|
||||||
colors_on: !no_colors,
|
colors_on: !no_colors,
|
||||||
by_filecount,
|
by_filecount,
|
||||||
num_chars_needed_on_left_most,
|
num_chars_needed_on_left_most,
|
||||||
base_size: root_node.size,
|
base_size: c.size,
|
||||||
longest_string_length,
|
longest_string_length,
|
||||||
ls_colors: LsColors::from_env().unwrap_or_default(),
|
ls_colors: LsColors::from_env().unwrap_or_default(),
|
||||||
};
|
};
|
||||||
let draw_data = DrawData {
|
let draw_data = DrawData {
|
||||||
indent: "".to_string(),
|
indent: "".to_string(),
|
||||||
percent_bar: first_size_bar,
|
percent_bar: first_size_bar.clone(),
|
||||||
display_data: &display_data,
|
display_data: &display_data,
|
||||||
};
|
};
|
||||||
display_node(root_node, &draw_data, true, true);
|
display_node(c, &draw_data, true, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_longest_dir_name(
|
fn find_longest_dir_name(node: &Node, indent: usize, terminal: usize, long_paths: bool) -> usize {
|
||||||
node: &DisplayNode,
|
|
||||||
indent: usize,
|
|
||||||
terminal: usize,
|
|
||||||
long_paths: bool,
|
|
||||||
) -> usize {
|
|
||||||
let printable_name = get_printable_name(&node.name, long_paths);
|
let printable_name = get_printable_name(&node.name, long_paths);
|
||||||
let longest = min(
|
let longest = min(
|
||||||
UnicodeWidthStr::width(&*printable_name) + 1 + indent,
|
UnicodeWidthStr::width(&*printable_name) + 1 + indent,
|
||||||
@@ -177,7 +179,7 @@ fn find_longest_dir_name(
|
|||||||
.fold(longest, max)
|
.fold(longest, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_node(node: DisplayNode, draw_data: &DrawData, is_biggest: bool, is_last: bool) {
|
fn display_node(node: Node, draw_data: &DrawData, is_biggest: bool, is_last: bool) {
|
||||||
// hacky way of working out how deep we are in the tree
|
// hacky way of working out how deep we are in the tree
|
||||||
let indent = draw_data.get_new_indent(!node.children.is_empty(), is_last);
|
let indent = draw_data.get_new_indent(!node.children.is_empty(), is_last);
|
||||||
let level = ((indent.chars().count() - 1) / 2) - 1;
|
let level = ((indent.chars().count() - 1) / 2) - 1;
|
||||||
@@ -252,18 +254,16 @@ fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool) -> String
|
|||||||
encode_u8(printable_name.display().to_string().as_bytes())
|
encode_u8(printable_name.display().to_string().as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &DisplayData) -> String {
|
fn pad_or_trim_filename(node: &Node, indent: &str, display_data: &DisplayData) -> String {
|
||||||
let name = get_printable_name(&node.name, display_data.short_paths);
|
let name = get_printable_name(&node.name, display_data.short_paths);
|
||||||
let indent_and_name = format!("{} {}", indent, name);
|
let indent_and_name = format!("{} {}", indent, name);
|
||||||
let width = UnicodeWidthStr::width(&*indent_and_name);
|
let width = UnicodeWidthStr::width(&*indent_and_name);
|
||||||
|
|
||||||
assert!(display_data.longest_string_length >= width);
|
|
||||||
|
|
||||||
// Add spaces after the filename so we can draw the % used bar chart.
|
// Add spaces after the filename so we can draw the % used bar chart.
|
||||||
let name_and_padding = name
|
let name_and_padding = name
|
||||||
+ " "
|
+ &(repeat(" ")
|
||||||
.repeat(display_data.longest_string_length - width)
|
.take(display_data.longest_string_length - width)
|
||||||
.as_str();
|
.collect::<String>());
|
||||||
|
|
||||||
maybe_trim_filename(name_and_padding, display_data)
|
maybe_trim_filename(name_and_padding, display_data)
|
||||||
}
|
}
|
||||||
@@ -281,7 +281,7 @@ fn maybe_trim_filename(name_in: String, display_data: &DisplayData) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_string(
|
pub fn format_string(
|
||||||
node: &DisplayNode,
|
node: &Node,
|
||||||
indent: &str,
|
indent: &str,
|
||||||
percent_bar: &str,
|
percent_bar: &str,
|
||||||
is_biggest: bool,
|
is_biggest: bool,
|
||||||
@@ -294,7 +294,7 @@ pub fn format_string(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_name_percent(
|
fn get_name_percent(
|
||||||
node: &DisplayNode,
|
node: &Node,
|
||||||
indent: &str,
|
indent: &str,
|
||||||
bar_chart: &str,
|
bar_chart: &str,
|
||||||
display_data: &DisplayData,
|
display_data: &DisplayData,
|
||||||
@@ -311,12 +311,12 @@ fn get_name_percent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayData) -> String {
|
fn get_pretty_size(node: &Node, is_biggest: bool, display_data: &DisplayData) -> String {
|
||||||
let output = if display_data.by_filecount {
|
let output = if display_data.by_filecount {
|
||||||
let size_as_str = node.size.separate_with_commas();
|
let size_as_str = node.size.separate_with_commas();
|
||||||
let spaces_to_add =
|
let spaces_to_add =
|
||||||
display_data.num_chars_needed_on_left_most - size_as_str.chars().count();
|
display_data.num_chars_needed_on_left_most - size_as_str.chars().count();
|
||||||
size_as_str + " ".repeat(spaces_to_add).as_str()
|
size_as_str + &*repeat(' ').take(spaces_to_add).collect::<String>()
|
||||||
} else {
|
} else {
|
||||||
format!("{:>5}", human_readable_number(node.size))
|
format!("{:>5}", human_readable_number(node.size))
|
||||||
};
|
};
|
||||||
@@ -328,11 +328,7 @@ fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pretty_name(
|
fn get_pretty_name(node: &Node, name_and_padding: String, display_data: &DisplayData) -> String {
|
||||||
node: &DisplayNode,
|
|
||||||
name_and_padding: String,
|
|
||||||
display_data: &DisplayData,
|
|
||||||
) -> String {
|
|
||||||
if display_data.colors_on {
|
if display_data.colors_on {
|
||||||
let meta_result = fs::metadata(node.name.clone());
|
let meta_result = fs::metadata(node.name.clone());
|
||||||
let directory_color = display_data
|
let directory_color = display_data
|
||||||
@@ -383,7 +379,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_str() {
|
fn test_format_str() {
|
||||||
let n = DisplayNode {
|
let n = Node {
|
||||||
name: PathBuf::from("/short"),
|
name: PathBuf::from("/short"),
|
||||||
size: 2_u64.pow(12), // This is 4.0K
|
size: 2_u64.pow(12), // This is 4.0K
|
||||||
children: vec![],
|
children: vec![],
|
||||||
@@ -405,7 +401,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_format_str_long_name() {
|
fn test_format_str_long_name() {
|
||||||
let name = "very_long_name_longer_than_the_eighty_character_limit_very_long_name_this_bit_will_truncate";
|
let name = "very_long_name_longer_than_the_eighty_character_limit_very_long_name_this_bit_will_truncate";
|
||||||
let n = DisplayNode {
|
let n = Node {
|
||||||
name: PathBuf::from(name),
|
name: PathBuf::from(name),
|
||||||
size: 2_u64.pow(12), // This is 4.0K
|
size: 2_u64.pow(12), // This is 4.0K
|
||||||
children: vec![],
|
children: vec![],
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
use std::cmp::Ordering;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, Clone)]
|
|
||||||
pub struct DisplayNode {
|
|
||||||
pub name: PathBuf, //todo: consider moving to a string?
|
|
||||||
pub size: u64,
|
|
||||||
pub children: Vec<DisplayNode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for DisplayNode {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
if self.size == other.size {
|
|
||||||
self.name.cmp(&other.name)
|
|
||||||
} else {
|
|
||||||
self.size.cmp(&other.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for DisplayNode {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for DisplayNode {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.name == other.name && self.size == other.size && self.children == other.children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DisplayNode {
|
|
||||||
pub fn num_siblings(&self) -> u64 {
|
|
||||||
self.children.len() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = DisplayNode> {
|
|
||||||
// we box to avoid the clippy lint warning
|
|
||||||
let out: Box<dyn Iterator<Item = DisplayNode>> = if is_reversed {
|
|
||||||
Box::new(self.children.clone().into_iter().rev())
|
|
||||||
} else {
|
|
||||||
Box::new(self.children.clone().into_iter())
|
|
||||||
};
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-174
@@ -1,174 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
pub fn get_by_depth(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode> {
|
|
||||||
if top_level_nodes.is_empty() {
|
|
||||||
// perhaps change this, bring back Error object?
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let root = get_new_root(top_level_nodes);
|
|
||||||
Some(build_by_depth(&root, n - 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut heap = BinaryHeap::new();
|
|
||||||
let number_top_level_nodes = top_level_nodes.len();
|
|
||||||
let root = get_new_root(top_level_nodes);
|
|
||||||
let mut allowed_nodes = HashSet::new();
|
|
||||||
|
|
||||||
allowed_nodes.insert(&root.name);
|
|
||||||
heap = add_children(using_a_filter, &root, heap);
|
|
||||||
|
|
||||||
for _ in number_top_level_nodes..n {
|
|
||||||
let line = heap.pop();
|
|
||||||
match line {
|
|
||||||
Some(line) => {
|
|
||||||
allowed_nodes.insert(&line.name);
|
|
||||||
heap = add_children(using_a_filter, line, heap);
|
|
||||||
}
|
|
||||||
None => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
let mut new_children: Vec<_> = node
|
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.map(|c| build_by_depth(c, depth - 1))
|
|
||||||
.collect();
|
|
||||||
new_children.sort();
|
|
||||||
new_children.reverse();
|
|
||||||
new_children
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
DisplayNode {
|
|
||||||
name: node.name.clone(),
|
|
||||||
size: node.size,
|
|
||||||
children: new_children,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_new_root(top_level_nodes: Vec<Node>) -> Node {
|
|
||||||
if top_level_nodes.len() > 1 {
|
|
||||||
let total_size = top_level_nodes.iter().map(|node| node.size).sum();
|
|
||||||
Node {
|
|
||||||
name: PathBuf::from("(total)"),
|
|
||||||
size: total_size,
|
|
||||||
children: top_level_nodes,
|
|
||||||
inode_device: None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
top_level_nodes.into_iter().next().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recursive_rebuilder<'a>(
|
|
||||||
allowed_nodes: &'a HashSet<&PathBuf>,
|
|
||||||
current: &Node,
|
|
||||||
) -> Option<DisplayNode> {
|
|
||||||
let mut new_children: Vec<_> = current
|
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.filter_map(|c| {
|
|
||||||
if allowed_nodes.contains(&c.name) {
|
|
||||||
recursive_rebuilder(allowed_nodes, c)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
new_children.sort();
|
|
||||||
new_children.reverse();
|
|
||||||
let newnode = DisplayNode {
|
|
||||||
name: current.name.clone(),
|
|
||||||
size: current.size,
|
|
||||||
children: new_children,
|
|
||||||
};
|
|
||||||
Some(newnode)
|
|
||||||
}
|
|
||||||
+71
-129
@@ -1,30 +1,19 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate rayon;
|
extern crate crossbeam_channel as channel;
|
||||||
extern crate regex;
|
extern crate ignore;
|
||||||
extern crate unicode_width;
|
extern crate unicode_width;
|
||||||
|
extern crate walkdir;
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use self::display::draw_it;
|
use self::display::draw_it;
|
||||||
|
use crate::utils::is_a_parent_of;
|
||||||
use clap::{App, AppSettings, Arg};
|
use clap::{App, AppSettings, Arg};
|
||||||
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::cmp::max;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use terminal_size::{terminal_size, Height, Width};
|
use terminal_size::{terminal_size, Height, Width};
|
||||||
use utils::get_filesystem_devices;
|
use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, Node};
|
||||||
use utils::simplify_dir_names;
|
|
||||||
|
|
||||||
mod dir_walker;
|
|
||||||
mod display;
|
mod display;
|
||||||
mod display_node;
|
|
||||||
mod filter;
|
|
||||||
mod node;
|
|
||||||
mod platform;
|
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
static DEFAULT_NUMBER_OF_LINES: usize = 30;
|
static DEFAULT_NUMBER_OF_LINES: usize = 30;
|
||||||
@@ -64,7 +53,6 @@ fn get_height_of_terminal() -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn get_width_of_terminal() -> usize {
|
fn get_width_of_terminal() -> usize {
|
||||||
// Windows CI runners detect a very low terminal width
|
// Windows CI runners detect a very low terminal width
|
||||||
if let Some((Width(w), Height(_h))) = terminal_size() {
|
if let Some((Width(w), Height(_h))) = terminal_size() {
|
||||||
@@ -74,28 +62,6 @@ 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() {
|
fn main() {
|
||||||
let default_height = get_height_of_terminal();
|
let default_height = get_height_of_terminal();
|
||||||
let def_num_str = default_height.to_string();
|
let def_num_str = default_height.to_string();
|
||||||
@@ -109,8 +75,7 @@ fn main() {
|
|||||||
.short("d")
|
.short("d")
|
||||||
.long("depth")
|
.long("depth")
|
||||||
.help("Depth to show")
|
.help("Depth to show")
|
||||||
.takes_value(true)
|
.takes_value(true),
|
||||||
.conflicts_with("number_of_lines"),
|
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("number_of_lines")
|
Arg::with_name("number_of_lines")
|
||||||
@@ -174,38 +139,8 @@ fn main() {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("ignore_hidden")
|
Arg::with_name("ignore_hidden")
|
||||||
.short("i") // Do not use 'h' this is used by 'help'
|
.short("i") // Do not use 'h' this is used by 'help'
|
||||||
.long("ignore_hidden") //TODO: fix change - -> _
|
.long("ignore_hidden")
|
||||||
.help("Do not display hidden files"),
|
.help("Obey .git_ignore rules & 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(
|
||||||
Arg::with_name("width")
|
Arg::with_name("width")
|
||||||
@@ -215,18 +150,16 @@ fn main() {
|
|||||||
.number_of_values(1)
|
.number_of_values(1)
|
||||||
.help("Specify width of output overriding the auto detection of terminal width"),
|
.help("Specify width of output overriding the auto detection of terminal width"),
|
||||||
)
|
)
|
||||||
.arg(Arg::with_name("inputs").multiple(true).default_value("."))
|
|
||||||
|
.arg(Arg::with_name("inputs").multiple(true))
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let target_dirs = options
|
let target_dirs = {
|
||||||
.values_of("inputs")
|
match options.values_of("inputs") {
|
||||||
.expect("Should be a default value here")
|
None => vec!["."],
|
||||||
.collect();
|
Some(r) => r.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) {
|
let number_of_lines = match value_t!(options.value_of("number_of_lines"), usize) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
@@ -248,64 +181,41 @@ fn main() {
|
|||||||
.map_err(|_| eprintln!("Ignoring bad value for depth"))
|
.map_err(|_| eprintln!("Ignoring bad value for depth"))
|
||||||
.ok()
|
.ok()
|
||||||
});
|
});
|
||||||
|
if options.is_present("depth") && number_of_lines != default_height {
|
||||||
|
eprintln!("Use either -n or -d. Not both");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let no_colors = init_color(options.is_present("no_colors"));
|
let no_colors = init_color(options.is_present("no_colors"));
|
||||||
let use_apparent_size = options.is_present("display_apparent_size");
|
let use_apparent_size = options.is_present("display_apparent_size");
|
||||||
let ignore_directories: Vec<PathBuf> = options
|
|
||||||
.values_of("ignore_directory")
|
|
||||||
.map(|i| i.map(PathBuf::from).collect())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
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 limit_filesystem = options.is_present("limit_filesystem");
|
||||||
|
let ignore_directories = match options.values_of("ignore_directory") {
|
||||||
|
Some(i) => Some(i.map(PathBuf::from).collect()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let by_filecount = options.is_present("by_filecount");
|
||||||
|
let show_hidden = !options.is_present("ignore_hidden");
|
||||||
|
|
||||||
let simplified_dirs = simplify_dir_names(target_dirs);
|
let simplified_dirs = simplify_dir_names(target_dirs);
|
||||||
let allowed_filesystems = {
|
let (errors, nodes) = get_dir_tree(
|
||||||
if limit_filesystem {
|
&simplified_dirs,
|
||||||
get_filesystem_devices(simplified_dirs.iter())
|
&ignore_directories,
|
||||||
} 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 walk_data = WalkData {
|
|
||||||
ignore_directories: ignored_full_path,
|
|
||||||
filter_regex: maybe_filter,
|
|
||||||
invert_filter_regex: maybe_invert_filter,
|
|
||||||
allowed_filesystems,
|
|
||||||
use_apparent_size,
|
use_apparent_size,
|
||||||
|
limit_filesystem,
|
||||||
by_filecount,
|
by_filecount,
|
||||||
ignore_hidden,
|
show_hidden,
|
||||||
};
|
);
|
||||||
|
let sorted_data = sort(nodes);
|
||||||
let (top_level_nodes, has_errors) = walk_it(simplified_dirs, walk_data);
|
let biggest_ones = {
|
||||||
|
match depth {
|
||||||
let tree = {
|
None => find_big_ones(sorted_data, number_of_lines),
|
||||||
match (depth, summarize_file_types) {
|
Some(_) => sorted_data,
|
||||||
(_, 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(),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let tree = build_tree(biggest_ones, depth);
|
||||||
|
|
||||||
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(
|
draw_it(
|
||||||
|
errors,
|
||||||
options.is_present("display_full_paths"),
|
options.is_present("display_full_paths"),
|
||||||
!options.is_present("reverse"),
|
!options.is_present("reverse"),
|
||||||
no_colors,
|
no_colors,
|
||||||
@@ -315,3 +225,35 @@ fn main() {
|
|||||||
tree,
|
tree,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_tree(biggest_ones: Vec<(PathBuf, u64)>, depth: Option<usize>) -> Node {
|
||||||
|
let mut top_parent = Node::default();
|
||||||
|
|
||||||
|
// assume sorted order
|
||||||
|
for b in biggest_ones {
|
||||||
|
let n = Node {
|
||||||
|
name: b.0,
|
||||||
|
size: b.1,
|
||||||
|
children: Vec::default(),
|
||||||
|
};
|
||||||
|
recursively_build_tree(&mut top_parent, n, depth);
|
||||||
|
}
|
||||||
|
top_parent
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recursively_build_tree(parent_node: &mut Node, new_node: Node, depth: Option<usize>) {
|
||||||
|
let new_depth = match depth {
|
||||||
|
None => None,
|
||||||
|
Some(0) => return,
|
||||||
|
Some(d) => Some(d - 1),
|
||||||
|
};
|
||||||
|
if let Some(c) = parent_node
|
||||||
|
.children
|
||||||
|
.iter_mut()
|
||||||
|
.find(|c| is_a_parent_of(&c.name, &new_node.name))
|
||||||
|
{
|
||||||
|
recursively_build_tree(c, new_node, new_depth);
|
||||||
|
} else {
|
||||||
|
parent_node.children.push(new_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
-79
@@ -1,79 +0,0 @@
|
|||||||
use crate::platform::get_metadata;
|
|
||||||
use crate::utils::is_filtered_out_due_to_invert_regex;
|
|
||||||
use crate::utils::is_filtered_out_due_to_regex;
|
|
||||||
|
|
||||||
use regex::Regex;
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, Clone)]
|
|
||||||
pub struct Node {
|
|
||||||
pub name: PathBuf,
|
|
||||||
pub size: u64,
|
|
||||||
pub children: Vec<Node>,
|
|
||||||
pub inode_device: Option<(u64, u64)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 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,
|
|
||||||
children,
|
|
||||||
inode_device,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Node {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.name == other.name && self.size == other.size && self.children == other.children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for Node {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
if self.size == other.size {
|
|
||||||
self.name.cmp(&other.name)
|
|
||||||
} else {
|
|
||||||
self.size.cmp(&other.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for Node {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-160
@@ -1,160 +0,0 @@
|
|||||||
use platform::get_metadata;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use crate::platform;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
|
|
||||||
let mut top_level_names: HashSet<PathBuf> = HashSet::with_capacity(filenames.len());
|
|
||||||
let mut to_remove: Vec<PathBuf> = Vec::with_capacity(filenames.len());
|
|
||||||
|
|
||||||
for t in filenames {
|
|
||||||
let top_level_name = normalize_path(t);
|
|
||||||
let mut can_add = true;
|
|
||||||
|
|
||||||
for tt in top_level_names.iter() {
|
|
||||||
if is_a_parent_of(&top_level_name, tt) {
|
|
||||||
to_remove.push(tt.to_path_buf());
|
|
||||||
} else if is_a_parent_of(tt, &top_level_name) {
|
|
||||||
can_add = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
to_remove.sort_unstable();
|
|
||||||
top_level_names.retain(|tr| to_remove.binary_search(tr).is_err());
|
|
||||||
to_remove.clear();
|
|
||||||
if can_add {
|
|
||||||
top_level_names.insert(top_level_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
top_level_names
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_filesystem_devices<'a, P: IntoIterator<Item = &'a PathBuf>>(paths: P) -> HashSet<u64> {
|
|
||||||
// Gets the device ids for the filesystems which are used by the argument paths
|
|
||||||
paths
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|p| {
|
|
||||||
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
|
|
||||||
// 2. removing interior '.' ("current directory") path segments
|
|
||||||
// 3. removing trailing extra separators and '.' ("current directory") path segments
|
|
||||||
// * `Path.components()` does all the above work; ref: <https://doc.rust-lang.org/std/path/struct.Path.html#method.components>
|
|
||||||
// 4. changing to os preferred separator (automatically done by recollecting components back into a PathBuf)
|
|
||||||
path.as_ref().components().collect::<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::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_dir() {
|
|
||||||
let mut correct = HashSet::new();
|
|
||||||
correct.insert(PathBuf::from("a"));
|
|
||||||
assert_eq!(simplify_dir_names(vec!["a"]), correct);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_dir_rm_subdir() {
|
|
||||||
let mut correct = HashSet::new();
|
|
||||||
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
|
||||||
assert_eq!(simplify_dir_names(vec!["a/b", "a/b/c", "a/b/d/f"]), correct);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_dir_duplicates() {
|
|
||||||
let mut correct = HashSet::new();
|
|
||||||
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
|
||||||
correct.insert(PathBuf::from("c"));
|
|
||||||
assert_eq!(
|
|
||||||
simplify_dir_names(vec![
|
|
||||||
"a/b",
|
|
||||||
"a/b//",
|
|
||||||
"a/././b///",
|
|
||||||
"c",
|
|
||||||
"c/",
|
|
||||||
"c/.",
|
|
||||||
"c/././",
|
|
||||||
"c/././."
|
|
||||||
]),
|
|
||||||
correct
|
|
||||||
);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_dir_rm_subdir_and_not_substrings() {
|
|
||||||
let mut correct = HashSet::new();
|
|
||||||
correct.insert(PathBuf::from("b"));
|
|
||||||
correct.insert(["c", "a", "b"].iter().collect::<PathBuf>());
|
|
||||||
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
|
||||||
assert_eq!(simplify_dir_names(vec!["a/b", "c/a/b/", "b"]), correct);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_dir_dots() {
|
|
||||||
let mut correct = HashSet::new();
|
|
||||||
correct.insert(PathBuf::from("src"));
|
|
||||||
assert_eq!(simplify_dir_names(vec!["src/."]), correct);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simplify_dir_substring_names() {
|
|
||||||
let mut correct = HashSet::new();
|
|
||||||
correct.insert(PathBuf::from("src"));
|
|
||||||
correct.insert(PathBuf::from("src_v2"));
|
|
||||||
assert_eq!(simplify_dir_names(vec!["src/", "src_v2"]), correct);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_a_parent_of() {
|
|
||||||
assert!(is_a_parent_of("/usr", "/usr/andy"));
|
|
||||||
assert!(is_a_parent_of("/usr", "/usr/andy/i/am/descendant"));
|
|
||||||
assert!(!is_a_parent_of("/usr", "/usr/."));
|
|
||||||
assert!(!is_a_parent_of("/usr", "/usr/"));
|
|
||||||
assert!(!is_a_parent_of("/usr", "/usr"));
|
|
||||||
assert!(!is_a_parent_of("/usr/", "/usr"));
|
|
||||||
assert!(!is_a_parent_of("/usr/andy", "/usr"));
|
|
||||||
assert!(!is_a_parent_of("/usr/andy", "/usr/sibling"));
|
|
||||||
assert!(!is_a_parent_of("/usr/folder", "/usr/folder_not_a_child"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_a_parent_of_root() {
|
|
||||||
assert!(is_a_parent_of("/", "/usr/andy"));
|
|
||||||
assert!(is_a_parent_of("/", "/usr"));
|
|
||||||
assert!(!is_a_parent_of("/", "/"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,402 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
use channel::Receiver;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
use ignore::{WalkBuilder, WalkState};
|
||||||
|
use std::sync::atomic;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
mod platform;
|
||||||
|
use self::platform::*;
|
||||||
|
|
||||||
|
type PathData = (PathBuf, u64, Option<(u64, u64)>);
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Eq, Clone)]
|
||||||
|
pub struct Node {
|
||||||
|
pub name: PathBuf,
|
||||||
|
pub size: u64,
|
||||||
|
pub children: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Node {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
if self.size == other.size {
|
||||||
|
self.name.cmp(&other.name)
|
||||||
|
} else {
|
||||||
|
self.size.cmp(&other.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Node {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Node {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.name == other.name && self.size == other.size && self.children == other.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
pub fn num_siblings(&self) -> u64 {
|
||||||
|
self.children.len() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = Node> {
|
||||||
|
if is_reversed {
|
||||||
|
let children: Vec<Node> = self.children.clone().into_iter().rev().collect();
|
||||||
|
children.into_iter()
|
||||||
|
} else {
|
||||||
|
self.children.clone().into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Errors {
|
||||||
|
pub permissions: bool,
|
||||||
|
pub not_found: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
|
||||||
|
let parent = parent.as_ref();
|
||||||
|
let child = child.as_ref();
|
||||||
|
child.starts_with(parent) && !parent.starts_with(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
let mut to_remove: Vec<PathBuf> = Vec::with_capacity(filenames.len());
|
||||||
|
|
||||||
|
for t in filenames {
|
||||||
|
let top_level_name = normalize_path(t);
|
||||||
|
let mut can_add = true;
|
||||||
|
|
||||||
|
for tt in top_level_names.iter() {
|
||||||
|
if is_a_parent_of(&top_level_name, tt) {
|
||||||
|
to_remove.push(tt.to_path_buf());
|
||||||
|
} else if is_a_parent_of(tt, &top_level_name) {
|
||||||
|
can_add = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
to_remove.sort_unstable();
|
||||||
|
top_level_names.retain(|tr| to_remove.binary_search(tr).is_err());
|
||||||
|
to_remove.clear();
|
||||||
|
if can_add {
|
||||||
|
top_level_names.insert(top_level_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
top_level_names
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_walk_dir_builder<P: AsRef<Path>>(
|
||||||
|
top_level_names: &HashSet<P>,
|
||||||
|
limit_filesystem: bool,
|
||||||
|
show_hidden: bool,
|
||||||
|
) -> WalkBuilder {
|
||||||
|
let mut it = top_level_names.iter();
|
||||||
|
let mut builder = WalkBuilder::new(it.next().unwrap());
|
||||||
|
builder.follow_links(false);
|
||||||
|
if show_hidden {
|
||||||
|
builder.hidden(false);
|
||||||
|
builder.ignore(false);
|
||||||
|
builder.git_global(false);
|
||||||
|
builder.git_ignore(false);
|
||||||
|
builder.git_exclude(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit_filesystem {
|
||||||
|
builder.same_file_system(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for b in it {
|
||||||
|
builder.add(b);
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_not_found(e: &ignore::Error) -> bool {
|
||||||
|
use ignore::Error;
|
||||||
|
if let Error::WithPath { err, .. } = e {
|
||||||
|
if let Error::Io(e) = &**err {
|
||||||
|
if e.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dir_tree<P: AsRef<Path>>(
|
||||||
|
top_level_names: &HashSet<P>,
|
||||||
|
ignore_directories: &Option<Vec<PathBuf>>,
|
||||||
|
apparent_size: bool,
|
||||||
|
limit_filesystem: bool,
|
||||||
|
by_filecount: bool,
|
||||||
|
show_hidden: bool,
|
||||||
|
) -> (Errors, HashMap<PathBuf, u64>) {
|
||||||
|
let (tx, rx) = channel::bounded::<PathData>(1000);
|
||||||
|
|
||||||
|
let permissions_flag = AtomicBool::new(false);
|
||||||
|
let not_found_flag = AtomicBool::new(false);
|
||||||
|
|
||||||
|
let t2 = top_level_names
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.as_ref().to_path_buf())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let t = create_reader_thread(rx, t2, apparent_size);
|
||||||
|
let walk_dir_builder = prepare_walk_dir_builder(top_level_names, limit_filesystem, show_hidden);
|
||||||
|
|
||||||
|
walk_dir_builder.build_parallel().run(|| {
|
||||||
|
let txc = tx.clone();
|
||||||
|
let pf = &permissions_flag;
|
||||||
|
let nf = ¬_found_flag;
|
||||||
|
Box::new(move |path| {
|
||||||
|
match path {
|
||||||
|
Ok(p) => {
|
||||||
|
if let Some(dirs) = ignore_directories {
|
||||||
|
let path = p.path();
|
||||||
|
let parts = path.components().collect::<Vec<std::path::Component>>();
|
||||||
|
for d in dirs {
|
||||||
|
if parts
|
||||||
|
.windows(d.components().count())
|
||||||
|
.any(|window| window.iter().collect::<PathBuf>() == *d)
|
||||||
|
{
|
||||||
|
return WalkState::Continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let maybe_size_and_inode = get_metadata(&p, apparent_size);
|
||||||
|
|
||||||
|
match maybe_size_and_inode {
|
||||||
|
Some(data) => {
|
||||||
|
let (size, inode_device) =
|
||||||
|
if by_filecount { (1, data.1) } else { data };
|
||||||
|
txc.send((p.into_path(), size, inode_device)).unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
pf.store(true, atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if is_not_found(&e) {
|
||||||
|
nf.store(true, atomic::Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
pf.store(true, atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
WalkState::Continue
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
drop(tx);
|
||||||
|
let data = t.join().unwrap();
|
||||||
|
let errors = Errors {
|
||||||
|
permissions: permissions_flag.load(atomic::Ordering::SeqCst),
|
||||||
|
not_found: not_found_flag.load(atomic::Ordering::SeqCst),
|
||||||
|
};
|
||||||
|
(errors, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_reader_thread(
|
||||||
|
rx: Receiver<PathData>,
|
||||||
|
top_level_names: HashSet<PathBuf>,
|
||||||
|
apparent_size: bool,
|
||||||
|
) -> JoinHandle<HashMap<PathBuf, u64>> {
|
||||||
|
// Receiver thread
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut hash: HashMap<PathBuf, u64> = HashMap::new();
|
||||||
|
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
|
||||||
|
|
||||||
|
for dent in rx {
|
||||||
|
let (path, size, maybe_inode_device) = dent;
|
||||||
|
|
||||||
|
if should_ignore_file(apparent_size, &mut inodes, maybe_inode_device) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
for p in path.ancestors() {
|
||||||
|
let s = hash.entry(p.to_path_buf()).or_insert(0);
|
||||||
|
*s += size;
|
||||||
|
|
||||||
|
if top_level_names.contains(p) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hash
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||||
|
// normalize path ...
|
||||||
|
// 1. removing repeated separators
|
||||||
|
// 2. removing interior '.' ("current directory") path segments
|
||||||
|
// 3. removing trailing extra separators and '.' ("current directory") path segments
|
||||||
|
// * `Path.components()` does all the above work; ref: <https://doc.rust-lang.org/std/path/struct.Path.html#method.components>
|
||||||
|
// 4. changing to os preferred separator (automatically done by recollecting components back into a PathBuf)
|
||||||
|
path.as_ref().components().collect::<PathBuf>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_ignore_file(
|
||||||
|
apparent_size: bool,
|
||||||
|
inodes: &mut HashSet<(u64, u64)>,
|
||||||
|
maybe_inode_device: Option<(u64, u64)>,
|
||||||
|
) -> bool {
|
||||||
|
match maybe_inode_device {
|
||||||
|
None => false,
|
||||||
|
Some(data) => {
|
||||||
|
let (inode, device) = data;
|
||||||
|
if !apparent_size {
|
||||||
|
// Ignore files already visited or symlinked
|
||||||
|
if inodes.contains(&(inode, device)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
inodes.insert((inode, device));
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort_by_size_first_name_second(a: &(PathBuf, u64), b: &(PathBuf, u64)) -> Ordering {
|
||||||
|
let result = b.1.cmp(&a.1);
|
||||||
|
if result == Ordering::Equal {
|
||||||
|
a.0.cmp(&b.0)
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort(data: HashMap<PathBuf, u64>) -> Vec<(PathBuf, u64)> {
|
||||||
|
let mut new_l: Vec<(PathBuf, u64)> = data.iter().map(|(a, b)| (a.clone(), *b)).collect();
|
||||||
|
new_l.sort_unstable_by(sort_by_size_first_name_second);
|
||||||
|
new_l
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_big_ones(new_l: Vec<(PathBuf, u64)>, max_to_show: usize) -> Vec<(PathBuf, u64)> {
|
||||||
|
if max_to_show > 0 && new_l.len() > max_to_show {
|
||||||
|
new_l[0..max_to_show].to_vec()
|
||||||
|
} else {
|
||||||
|
new_l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simplify_dir() {
|
||||||
|
let mut correct = HashSet::new();
|
||||||
|
correct.insert(PathBuf::from("a"));
|
||||||
|
assert_eq!(simplify_dir_names(vec!["a"]), correct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simplify_dir_rm_subdir() {
|
||||||
|
let mut correct = HashSet::new();
|
||||||
|
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
||||||
|
assert_eq!(simplify_dir_names(vec!["a/b", "a/b/c", "a/b/d/f"]), correct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simplify_dir_duplicates() {
|
||||||
|
let mut correct = HashSet::new();
|
||||||
|
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
||||||
|
correct.insert(PathBuf::from("c"));
|
||||||
|
assert_eq!(
|
||||||
|
simplify_dir_names(vec![
|
||||||
|
"a/b",
|
||||||
|
"a/b//",
|
||||||
|
"a/././b///",
|
||||||
|
"c",
|
||||||
|
"c/",
|
||||||
|
"c/.",
|
||||||
|
"c/././",
|
||||||
|
"c/././."
|
||||||
|
]),
|
||||||
|
correct
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_simplify_dir_rm_subdir_and_not_substrings() {
|
||||||
|
let mut correct = HashSet::new();
|
||||||
|
correct.insert(PathBuf::from("b"));
|
||||||
|
correct.insert(["c", "a", "b"].iter().collect::<PathBuf>());
|
||||||
|
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
||||||
|
assert_eq!(simplify_dir_names(vec!["a/b", "c/a/b/", "b"]), correct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simplify_dir_dots() {
|
||||||
|
let mut correct = HashSet::new();
|
||||||
|
correct.insert(PathBuf::from("src"));
|
||||||
|
assert_eq!(simplify_dir_names(vec!["src/."]), correct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simplify_dir_substring_names() {
|
||||||
|
let mut correct = HashSet::new();
|
||||||
|
correct.insert(PathBuf::from("src"));
|
||||||
|
correct.insert(PathBuf::from("src_v2"));
|
||||||
|
assert_eq!(simplify_dir_names(vec!["src/", "src_v2"]), correct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_a_parent_of() {
|
||||||
|
assert!(is_a_parent_of("/usr", "/usr/andy"));
|
||||||
|
assert!(is_a_parent_of("/usr", "/usr/andy/i/am/descendant"));
|
||||||
|
assert!(!is_a_parent_of("/usr", "/usr/."));
|
||||||
|
assert!(!is_a_parent_of("/usr", "/usr/"));
|
||||||
|
assert!(!is_a_parent_of("/usr", "/usr"));
|
||||||
|
assert!(!is_a_parent_of("/usr/", "/usr"));
|
||||||
|
assert!(!is_a_parent_of("/usr/andy", "/usr"));
|
||||||
|
assert!(!is_a_parent_of("/usr/andy", "/usr/sibling"));
|
||||||
|
assert!(!is_a_parent_of("/usr/folder", "/usr/folder_not_a_child"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_a_parent_of_root() {
|
||||||
|
assert!(is_a_parent_of("/", "/usr/andy"));
|
||||||
|
assert!(is_a_parent_of("/", "/usr"));
|
||||||
|
assert!(!is_a_parent_of("/", "/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_ignore_file() {
|
||||||
|
let mut files = HashSet::new();
|
||||||
|
files.insert((10, 20));
|
||||||
|
|
||||||
|
assert!(!should_ignore_file(true, &mut files, Some((0, 0))));
|
||||||
|
|
||||||
|
// New file is not known it will be inserted to the hashmp and should not be ignored
|
||||||
|
assert!(!should_ignore_file(false, &mut files, Some((11, 12))));
|
||||||
|
assert!(files.contains(&(11, 12)));
|
||||||
|
|
||||||
|
// The same file will be ignored the second time
|
||||||
|
assert!(should_ignore_file(false, &mut files, Some((11, 12))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_ignore_file_on_different_device() {
|
||||||
|
let mut files = HashSet::new();
|
||||||
|
files.insert((10, 20));
|
||||||
|
|
||||||
|
// We do not ignore files on the same device
|
||||||
|
assert!(!should_ignore_file(false, &mut files, Some((2, 99))));
|
||||||
|
assert!(!should_ignore_file(true, &mut files, Some((2, 99))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
|
use ignore::DirEntry;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn get_block_size() -> u64 {
|
fn get_block_size() -> u64 {
|
||||||
// All os specific implementations of MetatdataExt seem to define a block as 512 bytes
|
// All os specific implementations of MetatdataExt seem to define a block as 512 bytes
|
||||||
@@ -11,7 +10,7 @@ fn get_block_size() -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
match d.metadata() {
|
match d.metadata() {
|
||||||
Ok(md) => {
|
Ok(md) => {
|
||||||
@@ -26,7 +25,7 @@ pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "windows")]
|
#[cfg(target_family = "windows")]
|
||||||
pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
pub fn get_metadata(d: &DirEntry, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
||||||
// On windows opening the file to get size, file ID and volume can be very
|
// On windows opening the file to get size, file ID and volume can be very
|
||||||
// expensive because 1) it causes a few system calls, and more importantly 2) it can cause
|
// expensive because 1) it causes a few system calls, and more importantly 2) it can cause
|
||||||
// windows defender to scan the file.
|
// windows defender to scan the file.
|
||||||
@@ -64,6 +63,7 @@ pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(
|
|||||||
// With this optimization: 8 sec.
|
// With this optimization: 8 sec.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
use winapi_util::Handle;
|
use winapi_util::Handle;
|
||||||
fn handle_from_path_limited<P: AsRef<Path>>(path: P) -> io::Result<Handle> {
|
fn handle_from_path_limited<P: AsRef<Path>>(path: P) -> io::Result<Handle> {
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
@@ -90,10 +90,10 @@ pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(
|
|||||||
Ok(Handle::from_file(file))
|
Ok(Handle::from_file(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_metadata_expensive(d: &Path) -> Option<(u64, Option<(u64, u64)>)> {
|
fn get_metadata_expensive(d: &DirEntry) -> Option<(u64, Option<(u64, u64)>)> {
|
||||||
use winapi_util::file::information;
|
use winapi_util::file::information;
|
||||||
|
|
||||||
let h = handle_from_path_limited(d).ok()?;
|
let h = handle_from_path_limited(d.path()).ok()?;
|
||||||
let info = information(&h).ok()?;
|
let info = information(&h).ok()?;
|
||||||
|
|
||||||
Some((
|
Some((
|
||||||
@@ -120,9 +120,9 @@ pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(
|
|||||||
{
|
{
|
||||||
Some((md.len(), None))
|
Some((md.len(), None))
|
||||||
} else {
|
} else {
|
||||||
get_metadata_expensive(d)
|
get_metadata_expensive(&d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => get_metadata_expensive(d),
|
_ => get_metadata_expensive(&d),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+142
-64
@@ -1,5 +1,4 @@
|
|||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ fn copy_test_data(dir: &str) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize() {
|
pub fn initialize() {
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
copy_test_data("tests/test_dir");
|
copy_test_data("tests/test_dir");
|
||||||
copy_test_data("tests/test_dir2");
|
copy_test_data("tests/test_dir2");
|
||||||
@@ -46,149 +45,228 @@ 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
|
// "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)]
|
#[cfg_attr(target_os = "windows", ignore)]
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_main_basic() {
|
pub fn test_main_basic() {
|
||||||
// -c is no color mode - This makes testing much simpler
|
// -c is no color mode - This makes testing much simpler
|
||||||
exact_output_test(main_output(), vec!["-c", "/tmp/test_dir/"])
|
initialize();
|
||||||
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let assert = cmd.arg("-c").arg("/tmp/test_dir/").unwrap().stdout;
|
||||||
|
let output = str::from_utf8(&assert).unwrap();
|
||||||
|
assert!(output.contains(&main_output()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_os = "windows", ignore)]
|
#[cfg_attr(target_os = "windows", ignore)]
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_main_multi_arg() {
|
pub fn test_main_multi_arg() {
|
||||||
let command_args = vec![
|
initialize();
|
||||||
"-c",
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
"/tmp/test_dir/many/",
|
let assert = cmd
|
||||||
"/tmp/test_dir",
|
.arg("-c")
|
||||||
"/tmp/test_dir",
|
.arg("/tmp/test_dir/many/")
|
||||||
];
|
.arg("/tmp/test_dir")
|
||||||
exact_output_test(main_output(), command_args);
|
.arg("/tmp/test_dir")
|
||||||
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
|
let output = str::from_utf8(&assert).unwrap();
|
||||||
|
assert!(output.contains(&main_output()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_output() -> Vec<String> {
|
#[cfg(target_os = "macos")]
|
||||||
// Some linux currently thought to be Manjaro, Arch
|
fn main_output() -> String {
|
||||||
// Although probably depends on how drive is formatted
|
r#"
|
||||||
let mac_and_some_linux = r#"
|
|
||||||
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||||
4.0K ├── hello_file│████████████████████████████████████████████████ │ 100%
|
4.0K ├── hello_file│████████████████████████████████████████████████ │ 100%
|
||||||
4.0K ┌─┴ many │████████████████████████████████████████████████ │ 100%
|
4.0K ┌─┴ many │████████████████████████████████████████████████ │ 100%
|
||||||
4.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
|
4.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
|
||||||
"#
|
"#
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
let ubuntu = r#"
|
#[cfg(target_os = "linux")]
|
||||||
|
fn main_output() -> String {
|
||||||
|
r#"
|
||||||
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||||
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33%
|
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33%
|
||||||
8.0K ┌─┴ many │ █████████████████████████████████ │ 67%
|
8.0K ┌─┴ many │ █████████████████████████████████ │ 67%
|
||||||
12K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
|
12K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
|
||||||
"#
|
"#
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
vec![mac_and_some_linux, ubuntu]
|
#[cfg(target_os = "windows")]
|
||||||
|
fn main_output() -> String {
|
||||||
|
"windows results vary by host".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_os = "windows", ignore)]
|
#[cfg_attr(target_os = "windows", ignore)]
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_main_long_paths() {
|
pub fn test_main_long_paths() {
|
||||||
let command_args = vec!["-c", "-p", "/tmp/test_dir/"];
|
initialize();
|
||||||
exact_output_test(main_output_long_paths(), command_args);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let assert = cmd
|
||||||
|
.arg("-c")
|
||||||
|
.arg("-p")
|
||||||
|
.arg("/tmp/test_dir/")
|
||||||
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
|
let output = str::from_utf8(&assert).unwrap();
|
||||||
|
println!("{:?}", output.trim());
|
||||||
|
println!("{:?}", main_output_long_paths().trim());
|
||||||
|
assert!(output.contains(&main_output_long_paths()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_output_long_paths() -> Vec<String> {
|
#[cfg(target_os = "macos")]
|
||||||
let mac_and_some_linux = r#"
|
fn main_output_long_paths() -> String {
|
||||||
|
r#"
|
||||||
0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||||
4.0K ├── /tmp/test_dir/many/hello_file│█████████████████████████████ │ 100%
|
4.0K ├── /tmp/test_dir/many/hello_file│█████████████████████████████ │ 100%
|
||||||
4.0K ┌─┴ /tmp/test_dir/many │█████████████████████████████ │ 100%
|
4.0K ┌─┴ /tmp/test_dir/many │█████████████████████████████ │ 100%
|
||||||
4.0K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
|
4.0K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
|
||||||
"#
|
"#
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string()
|
||||||
let ubuntu = r#"
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn main_output_long_paths() -> String {
|
||||||
|
r#"
|
||||||
0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░█ │ 0%
|
0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||||
4.0K ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░██████████ │ 33%
|
4.0K ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░██████████ │ 33%
|
||||||
8.0K ┌─┴ /tmp/test_dir/many │ ████████████████████ │ 67%
|
8.0K ┌─┴ /tmp/test_dir/many │ ████████████████████ │ 67%
|
||||||
12K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
|
12K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
|
||||||
"#
|
"#
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string()
|
||||||
vec![mac_and_some_linux, ubuntu]
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn main_output_long_paths() -> String {
|
||||||
|
"windows results vary by host".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_os = "windows", ignore)]
|
||||||
|
#[test]
|
||||||
|
pub fn test_apparent_size() {
|
||||||
|
initialize();
|
||||||
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let assert = cmd.arg("-c").arg("-s").arg("/tmp/test_dir").unwrap().stdout;
|
||||||
|
let output = str::from_utf8(&assert).unwrap();
|
||||||
|
assert!(output.contains(&output_apparent_size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn output_apparent_size() -> String {
|
||||||
|
r#"
|
||||||
|
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||||
|
6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||||
|
4.0K ┌─┴ many │ █████████████████████████ │ 50%
|
||||||
|
8.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
|
||||||
|
"#
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn output_apparent_size() -> String {
|
||||||
|
r#"
|
||||||
|
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||||
|
6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░░░██ │ 3%
|
||||||
|
134B ┌─┴ many │ ████████████████████████████ │ 58%
|
||||||
|
230B ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
|
||||||
|
"#
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn output_apparent_size() -> String {
|
||||||
|
"windows results vary by host".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check against directories and files whos names are substrings of each other
|
// Check against directories and files whos names are substrings of each other
|
||||||
#[cfg_attr(target_os = "windows", ignore)]
|
#[cfg_attr(target_os = "windows", ignore)]
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_substring_of_names_and_long_names() {
|
pub fn test_substring_of_names_and_long_names() {
|
||||||
let command_args = vec!["-c", "/tmp/test_dir2"];
|
initialize();
|
||||||
exact_output_test(no_substring_of_names_output(), command_args);
|
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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn no_substring_of_names_output() -> Vec<String> {
|
#[cfg(target_os = "linux")]
|
||||||
let ubuntu = "
|
fn no_substring_of_names_output() -> String {
|
||||||
|
"
|
||||||
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
|
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
|
||||||
4.0K ├── dir_name_clash
|
4.0K ├── dir_name_clash
|
||||||
4.0K │ ┌── hello
|
4.0K │ ┌── hello
|
||||||
8.0K ├─┴ dir
|
|
||||||
4.0K │ ┌── hello
|
|
||||||
8.0K ├─┴ dir_substring
|
8.0K ├─┴ dir_substring
|
||||||
|
4.0K │ ┌── hello
|
||||||
|
8.0K ├─┴ dir
|
||||||
24K ┌─┴ test_dir2
|
24K ┌─┴ test_dir2
|
||||||
"
|
"
|
||||||
.trim()
|
.trim()
|
||||||
.into();
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
let mac_and_some_linux = "
|
#[cfg(target_os = "macos")]
|
||||||
|
fn no_substring_of_names_output() -> String {
|
||||||
|
"
|
||||||
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
|
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
|
||||||
4.0K │ ┌── hello
|
4.0K │ ┌── hello
|
||||||
4.0K ├─┴ dir
|
4.0K ├─┴ dir_substring
|
||||||
4.0K ├── dir_name_clash
|
4.0K ├── dir_name_clash
|
||||||
4.0K │ ┌── hello
|
4.0K │ ┌── hello
|
||||||
4.0K ├─┴ dir_substring
|
4.0K ├─┴ dir
|
||||||
12K ┌─┴ test_dir2
|
12K ┌─┴ test_dir2
|
||||||
"
|
"
|
||||||
.trim()
|
.trim()
|
||||||
.into();
|
.into()
|
||||||
vec![mac_and_some_linux, ubuntu]
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn no_substring_of_names_output() -> String {
|
||||||
|
"PRs".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_os = "windows", ignore)]
|
#[cfg_attr(target_os = "windows", ignore)]
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_unicode_directories() {
|
pub fn test_unicode_directories() {
|
||||||
let command_args = vec!["-c", "/tmp/test_dir_unicode"];
|
initialize();
|
||||||
exact_output_test(unicode_dir(), command_args);
|
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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unicode_dir() -> Vec<String> {
|
#[cfg(target_os = "linux")]
|
||||||
|
fn unicode_dir() -> String {
|
||||||
// The way unicode & asian characters are rendered on the terminal should make this line up
|
// The way unicode & asian characters are rendered on the terminal should make this line up
|
||||||
let ubuntu = "
|
"
|
||||||
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
|
0B ┌── 👩.unicode │ █ │ 0%
|
||||||
0B ├── 👩.unicode │ █ │ 0%
|
0B ├── ラウトは難しいです!.japan│ █ │ 0%
|
||||||
4.0K ┌─┴ test_dir_unicode │██████████████████████████████████ │ 100%
|
4.0K ┌─┴ test_dir_unicode │██████████████████████████████████ │ 100%
|
||||||
"
|
"
|
||||||
.trim()
|
.trim()
|
||||||
.into();
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
let mac_and_some_linux = "
|
#[cfg(target_os = "macos")]
|
||||||
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
|
fn unicode_dir() -> String {
|
||||||
0B ├── 👩.unicode │ █ │ 0%
|
"
|
||||||
|
0B ┌── 👩.unicode │ █ │ 0%
|
||||||
|
0B ├── ラウトは難しいです!.japan│ █ │ 0%
|
||||||
0B ┌─┴ test_dir_unicode │ █ │ 0%
|
0B ┌─┴ test_dir_unicode │ █ │ 0%
|
||||||
"
|
"
|
||||||
.trim()
|
.trim()
|
||||||
.into();
|
.into()
|
||||||
vec![mac_and_some_linux, ubuntu]
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn unicode_dir() -> String {
|
||||||
|
"".into()
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-79
@@ -1,25 +1,17 @@
|
|||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains tests that test a substring of the output using '.contains'
|
* This file contains tests that test a substring of the output using '.contains'
|
||||||
*
|
*
|
||||||
* These tests should be the same cross platform
|
* 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
|
// We can at least test the file names are there
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_basic_output() {
|
pub fn test_basic_output() {
|
||||||
let output = build_command(vec!["tests/test_dir/"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let output = cmd.arg("tests/test_dir/").unwrap().stdout;
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
|
|
||||||
assert!(output.contains(" ┌─┴ "));
|
assert!(output.contains(" ┌─┴ "));
|
||||||
assert!(output.contains("test_dir "));
|
assert!(output.contains("test_dir "));
|
||||||
@@ -33,7 +25,9 @@ pub fn test_basic_output() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_output_no_bars_means_no_excess_spaces() {
|
pub fn test_output_no_bars_means_no_excess_spaces() {
|
||||||
let output = build_command(vec!["-b", "tests/test_dir/"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let output = cmd.arg("-b").arg("tests/test_dir/").unwrap().stdout;
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
// If bars are not being shown we don't need to pad the output with spaces
|
// If bars are not being shown we don't need to pad the output with spaces
|
||||||
assert!(output.contains("many"));
|
assert!(output.contains("many"));
|
||||||
assert!(!output.contains("many "));
|
assert!(!output.contains("many "));
|
||||||
@@ -41,7 +35,15 @@ pub fn test_output_no_bars_means_no_excess_spaces() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_reverse_flag() {
|
pub fn test_reverse_flag() {
|
||||||
let output = build_command(vec!["-r", "-c", "tests/test_dir/"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let output = cmd
|
||||||
|
.arg("-c")
|
||||||
|
.arg("-r")
|
||||||
|
.arg("tests/test_dir/")
|
||||||
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
|
|
||||||
assert!(output.contains(" └─┬ test_dir "));
|
assert!(output.contains(" └─┬ test_dir "));
|
||||||
assert!(output.contains(" └─┬ many "));
|
assert!(output.contains(" └─┬ many "));
|
||||||
assert!(output.contains(" ├── hello_file"));
|
assert!(output.contains(" ├── hello_file"));
|
||||||
@@ -51,7 +53,15 @@ pub fn test_reverse_flag() {
|
|||||||
#[test]
|
#[test]
|
||||||
pub fn test_d_flag_works() {
|
pub fn test_d_flag_works() {
|
||||||
// We should see the top level directory but not the sub dirs / files:
|
// We should see the top level directory but not the sub dirs / files:
|
||||||
let output = build_command(vec!["-d", "1", "tests/test_dir/"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let output = cmd
|
||||||
|
.arg("-d")
|
||||||
|
.arg("1")
|
||||||
|
.arg("-s")
|
||||||
|
.arg("tests/test_dir/")
|
||||||
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
assert!(!output.contains("hello_file"));
|
assert!(!output.contains("hello_file"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,14 +69,31 @@ pub fn test_d_flag_works() {
|
|||||||
pub fn test_d_flag_works_and_still_recurses_down() {
|
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
|
// We had a bug where running with '-d 1' would stop at the first directory and the code
|
||||||
// would fail to recurse down
|
// would fail to recurse down
|
||||||
let output = build_command(vec!["-d", "1", "-f", "-c", "tests/test_dir2/"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
assert!(output.contains("4 ┌─┴ test_dir2"));
|
let output = cmd
|
||||||
|
.arg("-d")
|
||||||
|
.arg("1")
|
||||||
|
.arg("-f")
|
||||||
|
.arg("-c")
|
||||||
|
.arg("tests/test_dir2/")
|
||||||
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
|
assert!(output.contains("7 ┌─┴ test_dir2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check against directories and files whos names are substrings of each other
|
// Check against directories and files whos names are substrings of each other
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_ignore_dir() {
|
pub fn test_ignore_dir() {
|
||||||
let output = build_command(vec!["-c", "-X", "dir_substring", "tests/test_dir2/"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let output = cmd
|
||||||
|
.arg("-c")
|
||||||
|
.arg("-X")
|
||||||
|
.arg("dir_substring")
|
||||||
|
.arg("tests/test_dir2")
|
||||||
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
assert!(!output.contains("dir_substring"));
|
assert!(!output.contains("dir_substring"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,12 +108,25 @@ pub fn test_with_bad_param() {
|
|||||||
#[test]
|
#[test]
|
||||||
pub fn test_hidden_flag() {
|
pub fn test_hidden_flag() {
|
||||||
// Check we can see the hidden file normally
|
// Check we can see the hidden file normally
|
||||||
let output = build_command(vec!["-c", "tests/test_dir_hidden_entries/"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let output = cmd
|
||||||
|
.arg("-c")
|
||||||
|
.arg("tests/test_dir_hidden_entries")
|
||||||
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
assert!(output.contains(".hidden_file"));
|
assert!(output.contains(".hidden_file"));
|
||||||
assert!(output.contains("┌─┴ test_dir_hidden_entries"));
|
assert!(output.contains("┌─┴ test_dir_hidden_entries"));
|
||||||
|
|
||||||
// Check that adding the '-h' flag causes us to not see hidden files
|
// Check that adding the '-h' flag causes us to not see hidden files
|
||||||
let output = build_command(vec!["-c", "-i", "tests/test_dir_hidden_entries/"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
|
let output = cmd
|
||||||
|
.arg("-c")
|
||||||
|
.arg("-i")
|
||||||
|
.arg("tests/test_dir_hidden_entries")
|
||||||
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
assert!(!output.contains(".hidden_file"));
|
assert!(!output.contains(".hidden_file"));
|
||||||
assert!(output.contains("┌── test_dir_hidden_entries"));
|
assert!(output.contains("┌── test_dir_hidden_entries"));
|
||||||
}
|
}
|
||||||
@@ -94,64 +134,16 @@ pub fn test_hidden_flag() {
|
|||||||
#[test]
|
#[test]
|
||||||
pub fn test_number_of_files() {
|
pub fn test_number_of_files() {
|
||||||
// Check we can see the hidden file normally
|
// Check we can see the hidden file normally
|
||||||
let output = build_command(vec!["-c", "-f", "tests/test_dir"]);
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
assert!(output.contains("1 ┌── a_file "));
|
let output = cmd
|
||||||
assert!(output.contains("1 ├── hello_file"));
|
.arg("-c")
|
||||||
assert!(output.contains("2 ┌─┴ many"));
|
.arg("-f")
|
||||||
assert!(output.contains("2 ┌─┴ test_dir"));
|
.arg("tests/test_dir")
|
||||||
}
|
.unwrap()
|
||||||
|
.stdout;
|
||||||
#[cfg_attr(target_os = "windows", ignore)]
|
let output = str::from_utf8(&output).unwrap();
|
||||||
#[test]
|
assert!(output.contains("1 ┌── hello_file"));
|
||||||
pub fn test_apparent_size() {
|
assert!(output.contains("1 ├── a_file "));
|
||||||
// Check the '-s' Flag gives us byte sizes and that it doesn't round up to a block
|
assert!(output.contains("3 ┌─┴ many"));
|
||||||
let command_args = vec!["-c", "-s", "/tmp/test_dir"];
|
assert!(output.contains("4 ┌─┴ 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"));
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-12
@@ -2,6 +2,7 @@ use assert_cmd::Command;
|
|||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::panic;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
@@ -60,12 +61,12 @@ pub fn test_soft_sym_link() {
|
|||||||
.output();
|
.output();
|
||||||
assert!(c.is_ok());
|
assert!(c.is_ok());
|
||||||
|
|
||||||
let c = format!(" ├── {}", get_file_name(link_name_s.into()));
|
let c = format!(" ┌── {}", get_file_name(link_name_s.into()));
|
||||||
let b = format!(" ┌── {}", get_file_name(file_path_s.into()));
|
let b = format!(" ├── {}", get_file_name(file_path_s.into()));
|
||||||
let a = format!("─┴ {}", dir_s);
|
let a = format!("─┴ {}", dir_s);
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("dust").unwrap();
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
let output = cmd.arg("-p").arg("-c").arg("-s").arg(dir_s).unwrap().stdout;
|
let output = cmd.arg("-p").arg("-c").arg(dir_s).unwrap().stdout;
|
||||||
|
|
||||||
let output = str::from_utf8(&output).unwrap();
|
let output = str::from_utf8(&output).unwrap();
|
||||||
|
|
||||||
@@ -124,16 +125,9 @@ pub fn test_recursive_sym_link() {
|
|||||||
let b = format!(" └── {}", get_file_name(link_name_s.into()));
|
let b = format!(" └── {}", get_file_name(link_name_s.into()));
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("dust").unwrap();
|
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||||
let output = cmd
|
let output = cmd.arg("-p").arg("-c").arg("-r").arg(dir_s).unwrap().stdout;
|
||||||
.arg("-p")
|
|
||||||
.arg("-c")
|
|
||||||
.arg("-r")
|
|
||||||
.arg("-s")
|
|
||||||
.arg(dir_s)
|
|
||||||
.unwrap()
|
|
||||||
.stdout;
|
|
||||||
let output = str::from_utf8(&output).unwrap();
|
|
||||||
|
|
||||||
|
let output = str::from_utf8(&output).unwrap();
|
||||||
assert!(output.contains(a.as_str()));
|
assert!(output.contains(a.as_str()));
|
||||||
assert!(output.contains(b.as_str()));
|
assert!(output.contains(b.as_str()));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user