mirror of
https://github.com/bootandy/dust.git
synced 2025-12-06 04:41:09 -08:00
Compare commits
197 Commits
v0.4.41
...
fix_error_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d0206a5a5 | ||
|
|
dfce0eec66 | ||
|
|
c148cd9044 | ||
|
|
2893f73f47 | ||
|
|
5103ebe0d8 | ||
|
|
40acc8f868 | ||
|
|
eebd9daf2a | ||
|
|
9bc1a6d625 | ||
|
|
d6f9bb3c47 | ||
|
|
f70f4b7e12 | ||
|
|
b9b2aee760 | ||
|
|
f60184ecbb | ||
|
|
81d52e6e3a | ||
|
|
5980858b39 | ||
|
|
ed6a8d0462 | ||
|
|
4cef6aaa84 | ||
|
|
d477145694 | ||
|
|
dc5b7b2c2e | ||
|
|
cf5ebd76fe | ||
|
|
fc548919c5 | ||
|
|
4b4bca52d9 | ||
|
|
2a9d545c3c | ||
|
|
20cc5cf7e0 | ||
|
|
5fcc45efbe | ||
|
|
282f6d314d | ||
|
|
c36ca33fe9 | ||
|
|
34ba99af2a | ||
|
|
2713445ad0 | ||
|
|
b62f35291d | ||
|
|
a7fbcb8156 | ||
|
|
a7120b949c | ||
|
|
812e1e3c53 | ||
|
|
4eb3f29565 | ||
|
|
d64092d8a1 | ||
|
|
77750c8149 | ||
|
|
b9386cd39e | ||
|
|
17112b09cc | ||
|
|
c5adff5348 | ||
|
|
ad2e52e211 | ||
|
|
164bec71a3 | ||
|
|
11b5c7227f | ||
|
|
fc70f9ba30 | ||
|
|
a00d1f0719 | ||
|
|
c4ea7815f8 | ||
|
|
afc36a633f | ||
|
|
7275b273d4 | ||
|
|
a3e59f9c25 | ||
|
|
48bf656123 | ||
|
|
fabb27908d | ||
|
|
52aeeebe1f | ||
|
|
1e27288ec2 | ||
|
|
9f4a5daee6 | ||
|
|
27f0a015ef | ||
|
|
20d89bef91 | ||
|
|
469e6d0a69 | ||
|
|
2d58609d54 | ||
|
|
109a0b90d4 | ||
|
|
ab67c1a50e | ||
|
|
6a34b52d15 | ||
|
|
f708305190 | ||
|
|
2749f56b7a | ||
|
|
d983175189 | ||
|
|
4b3dc3988d | ||
|
|
fa4405b58b | ||
|
|
abb08f8e1a | ||
|
|
9f91d446c1 | ||
|
|
1b07c3c4f3 | ||
|
|
e55b917c96 | ||
|
|
0301c7a058 | ||
|
|
bbed8d7478 | ||
|
|
17e7390e25 | ||
|
|
1cb731533b | ||
|
|
d6c2482150 | ||
|
|
9d2e6d2b36 | ||
|
|
124c19b5c9 | ||
|
|
d8a334df3b | ||
|
|
c485e84145 | ||
|
|
f51e9dd222 | ||
|
|
c25f7d342c | ||
|
|
ca0a93f222 | ||
|
|
9cf260e42b | ||
|
|
87b1f50b39 | ||
|
|
3458c98bd0 | ||
|
|
2ad420d370 | ||
|
|
2047f99c6d | ||
|
|
9bd2f9fc2a | ||
|
|
5116c1c8a1 | ||
|
|
07ffd04950 | ||
|
|
dfa574375b | ||
|
|
9de2e7d723 | ||
|
|
8fddb24165 | ||
|
|
ed3902f07e | ||
|
|
f219a752d6 | ||
|
|
f6e36aba52 | ||
|
|
c286b8ba97 | ||
|
|
3dad7abfb8 | ||
|
|
42163abb73 | ||
|
|
8e0188c755 | ||
|
|
555d86206d | ||
|
|
02392881c5 | ||
|
|
6f1175ef8d | ||
|
|
1d062cf683 | ||
|
|
be7a9181b2 | ||
|
|
9dcd4d0de4 | ||
|
|
00c7ce8f15 | ||
|
|
c4a73d5921 | ||
|
|
e1ffc92589 | ||
|
|
18729762ad | ||
|
|
8a499201de | ||
|
|
551c5d3bfa | ||
|
|
15a867636f | ||
|
|
1b3d0b2724 | ||
|
|
d5fa7f0861 | ||
|
|
e15cf0c42e | ||
|
|
1891de7fa3 | ||
|
|
998e7fb2f8 | ||
|
|
a48c7782ac | ||
|
|
e0347b0b43 | ||
|
|
5b6b449cbd | ||
|
|
c8568e5674 | ||
|
|
9d13994526 | ||
|
|
ca8b1efc18 | ||
|
|
c261492325 | ||
|
|
8d43185439 | ||
|
|
1d018bc80b | ||
|
|
2319c3032f | ||
|
|
53af0d486d | ||
|
|
b643abe05e | ||
|
|
695c9b6747 | ||
|
|
8e8f18b9bc | ||
|
|
e767be217a | ||
|
|
9187cb8ad2 | ||
|
|
873456eb97 | ||
|
|
4e1180e502 | ||
|
|
4ea8d9339e | ||
|
|
82237c6bde | ||
|
|
109df305a3 | ||
|
|
d03a9796f4 | ||
|
|
8e591f3dd5 | ||
|
|
086f11988c | ||
|
|
52eca1ff78 | ||
|
|
e80c60b027 | ||
|
|
e118814684 | ||
|
|
1546cf2eba | ||
|
|
4f06de8044 | ||
|
|
2ca6fcc9ce | ||
|
|
278a31971c | ||
|
|
479e5899c6 | ||
|
|
c4c173e40e | ||
|
|
b8410ee4da | ||
|
|
0cb0396a84 | ||
|
|
b1fe078e06 | ||
|
|
b3d446bfef | ||
|
|
34be81c216 | ||
|
|
342164d357 | ||
|
|
bfa3594fe8 | ||
|
|
1f120de168 | ||
|
|
c0048b2ae4 | ||
|
|
402a8f8249 | ||
|
|
7cc7047b28 | ||
|
|
1953e107c2 | ||
|
|
f096e82754 | ||
|
|
c3415df4b1 | ||
|
|
9452049aff | ||
|
|
09e3c27e70 | ||
|
|
b0bfe654c4 | ||
|
|
6bc44de495 | ||
|
|
efb455c739 | ||
|
|
2c58041885 | ||
|
|
c30f31c22c | ||
|
|
59f2cdfb84 | ||
|
|
795d91237d | ||
|
|
26ae050f16 | ||
|
|
58b395e7ee | ||
|
|
3beb2b5274 | ||
|
|
19d938b05e | ||
|
|
d4daa82297 | ||
|
|
21097578d9 | ||
|
|
069dc184a9 | ||
|
|
02fa657128 | ||
|
|
bc0e376c88 | ||
|
|
d7c602a2d2 | ||
|
|
603e6be7eb | ||
|
|
efa469e12f | ||
|
|
657858df16 | ||
|
|
a8d700d530 | ||
|
|
c408d8887d | ||
|
|
be2250d241 | ||
|
|
b6aa1378de | ||
|
|
2082141dfc | ||
|
|
8a9b5e889d | ||
|
|
871b7e90d8 | ||
|
|
edf300893c | ||
|
|
a3d8fc00e1 | ||
|
|
9d4531d48b | ||
|
|
75e419e7ed | ||
|
|
4a62fc5726 |
447
.github/workflows/CICD.yml
vendored
447
.github/workflows/CICD.yml
vendored
@@ -22,50 +22,56 @@ jobs:
|
||||
- { os: macos-latest }
|
||||
- { os: windows-latest }
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
# 'windows-latest' `cargo fmt` is bugged for this project (see reasons @ GH:rust-lang/rustfmt #3324, #3590, #3688 ; waiting for repair)
|
||||
JOB_DO_FORMAT_TESTING="true"
|
||||
case ${{ matrix.job.os }} in windows-latest) unset JOB_DO_FORMAT_TESTING ;; esac;
|
||||
echo set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING:-<empty>/false}
|
||||
echo ::set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING}
|
||||
- name: Install `rust` toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
components: rustfmt, clippy
|
||||
- name: "`fmt` testing"
|
||||
if: steps.vars.outputs.JOB_DO_FORMAT_TESTING
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- name: "`clippy` testing"
|
||||
if: success() || failure() # run regardless of prior step ("`fmt` testing") success/failure
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}" -- -D warnings
|
||||
- uses: actions/checkout@v1
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
# 'windows-latest' `cargo fmt` is bugged for this project (see reasons @ GH:rust-lang/rustfmt #3324, #3590, #3688 ; waiting for repair)
|
||||
JOB_DO_FORMAT_TESTING="true"
|
||||
case ${{ matrix.job.os }} in windows-latest) unset JOB_DO_FORMAT_TESTING ;; esac;
|
||||
echo set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING:-<empty>/false}
|
||||
echo ::set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING}
|
||||
# target-specific options
|
||||
# * CARGO_FEATURES_OPTION
|
||||
CARGO_FEATURES_OPTION='' ;
|
||||
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
|
||||
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
|
||||
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
|
||||
- name: Install `rust` toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
components: rustfmt, clippy
|
||||
- name: "`fmt` testing"
|
||||
if: steps.vars.outputs.JOB_DO_FORMAT_TESTING
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- name: "`clippy` testing"
|
||||
if: success() || failure() # run regardless of prior step ("`fmt` testing") success/failure
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings
|
||||
|
||||
min_version:
|
||||
name: MinSRV # Minimum supported rust version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_MIN_SRV }}
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_MIN_SRV }}
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
||||
build:
|
||||
name: Build
|
||||
@@ -75,161 +81,216 @@ jobs:
|
||||
matrix:
|
||||
job:
|
||||
# { os, target, cargo-options, features, use-cross, toolchain }
|
||||
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , use-cross: use-cross }
|
||||
- { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , use-cross: use-cross }
|
||||
- { os: ubuntu-18.04 , target: i686-unknown-linux-musl , use-cross: use-cross }
|
||||
- { os: ubuntu-18.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
|
||||
- { os: ubuntu-18.04 , target: x86_64-unknown-linux-musl , use-cross: use-cross }
|
||||
- { os: ubuntu-16.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
|
||||
- { os: macos-latest , target: x86_64-apple-darwin }
|
||||
- { os: windows-latest , target: i686-pc-windows-gnu }
|
||||
- { os: windows-latest , target: i686-pc-windows-msvc }
|
||||
- { os: windows-latest , target: x86_64-pc-windows-gnu } ## !maint: [rivy; 2020-01-21] may break due to rust bug; follow possible solution from GH:rust-lang/rust#47048 (refs: GH:rust-lang/rust#47048 , GH:rust-lang/rust#53454 , GH:bike-barn/hermit#172 )
|
||||
- { os: windows-latest , target: x86_64-pc-windows-msvc }
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
target: aarch64-unknown-linux-gnu,
|
||||
use-cross: use-cross,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
target: aarch64-unknown-linux-musl,
|
||||
use-cross: use-cross,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
target: arm-unknown-linux-gnueabihf,
|
||||
use-cross: use-cross,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
target: i686-unknown-linux-gnu,
|
||||
use-cross: use-cross,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
target: i686-unknown-linux-musl,
|
||||
use-cross: use-cross,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
use-cross: use-cross,
|
||||
}
|
||||
- {
|
||||
os: ubuntu-latest,
|
||||
target: x86_64-unknown-linux-musl,
|
||||
use-cross: use-cross,
|
||||
}
|
||||
- { os: macos-latest, target: x86_64-apple-darwin }
|
||||
- { os: windows-latest, target: i686-pc-windows-gnu }
|
||||
- { os: windows-latest, target: i686-pc-windows-msvc }
|
||||
- { os: windows-latest, target: x86_64-pc-windows-gnu } ## !maint: [rivy; 2020-01-21] may break due to rust bug; follow possible solution from GH:rust-lang/rust#47048 (refs: GH:rust-lang/rust#47048 , GH:rust-lang/rust#53454 , GH:bike-barn/hermit#172 )
|
||||
- { os: windows-latest, target: x86_64-pc-windows-msvc }
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install any prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
case ${{ matrix.job.target }} in
|
||||
arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
|
||||
esac
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
# toolchain
|
||||
TOOLCHAIN="stable" ## default to "stable" toolchain
|
||||
# * specify alternate TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: <https://github.com/rust-lang/rust/issues/47048>, <https://github.com/rust-lang/rust/issues/53454>, <https://github.com/rust-lang/cargo/issues/6754>)
|
||||
case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
|
||||
# * use requested TOOLCHAIN if specified
|
||||
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
|
||||
echo set-output name=TOOLCHAIN::${TOOLCHAIN}
|
||||
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
|
||||
# staging directory
|
||||
STAGING='_staging'
|
||||
echo set-output name=STAGING::${STAGING}
|
||||
echo ::set-output name=STAGING::${STAGING}
|
||||
# determine EXE suffix
|
||||
EXE_suffix="" ; case ${{ matrix.job.target }} in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
|
||||
echo set-output name=EXE_suffix::${EXE_suffix}
|
||||
echo ::set-output name=EXE_suffix::${EXE_suffix}
|
||||
# parse commit reference info
|
||||
REF_NAME=${GITHUB_REF#refs/*/}
|
||||
unset REF_BRANCH ; case ${GITHUB_REF} in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac;
|
||||
unset REF_TAG ; case ${GITHUB_REF} in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
|
||||
REF_SHAS=${GITHUB_SHA:0:8}
|
||||
echo set-output name=REF_NAME::${REF_NAME}
|
||||
echo set-output name=REF_BRANCH::${REF_BRANCH}
|
||||
echo set-output name=REF_TAG::${REF_TAG}
|
||||
echo set-output name=REF_SHAS::${REF_SHAS}
|
||||
echo ::set-output name=REF_NAME::${REF_NAME}
|
||||
echo ::set-output name=REF_BRANCH::${REF_BRANCH}
|
||||
echo ::set-output name=REF_TAG::${REF_TAG}
|
||||
echo ::set-output name=REF_SHAS::${REF_SHAS}
|
||||
# parse target
|
||||
unset TARGET_ARCH ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) TARGET_ARCH=arm ;; i686-*) TARGET_ARCH=i686 ;; x86_64-*) TARGET_ARCH=x86_64 ;; esac;
|
||||
echo set-output name=TARGET_ARCH::${TARGET_ARCH}
|
||||
echo ::set-output name=TARGET_ARCH::${TARGET_ARCH}
|
||||
unset TARGET_OS ; case ${{ matrix.job.target }} in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac;
|
||||
echo set-output name=TARGET_OS::${TARGET_OS}
|
||||
echo ::set-output name=TARGET_OS::${TARGET_OS}
|
||||
# package name
|
||||
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
|
||||
PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }}
|
||||
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
|
||||
echo set-output name=PKG_suffix::${PKG_suffix}
|
||||
echo set-output name=PKG_BASENAME::${PKG_BASENAME}
|
||||
echo set-output name=PKG_NAME::${PKG_NAME}
|
||||
echo ::set-output name=PKG_suffix::${PKG_suffix}
|
||||
echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
|
||||
echo ::set-output name=PKG_NAME::${PKG_NAME}
|
||||
# deployable tag? (ie, leading "vM" or "M"; M == version number)
|
||||
unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
|
||||
echo set-output name=DEPLOY::${DEPLOY:-<empty>/false}
|
||||
echo ::set-output name=DEPLOY::${DEPLOY}
|
||||
# target-specific options
|
||||
# * CARGO_USE_CROSS (truthy)
|
||||
CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
|
||||
echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-<empty>/false}
|
||||
echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS}
|
||||
# # * `arm` cannot be tested on ubuntu-* hosts (b/c testing is currently primarily done via comparison of target outputs with built-in outputs and the `arm` target is not executable on the host)
|
||||
JOB_DO_TESTING="true"
|
||||
case ${{ matrix.job.target }} in arm-*) unset JOB_DO_TESTING ;; esac;
|
||||
echo set-output name=JOB_DO_TESTING::${JOB_DO_TESTING:-<empty>/false}
|
||||
echo ::set-output name=JOB_DO_TESTING::${JOB_DO_TESTING}
|
||||
# # * test only binary for arm-type targets
|
||||
unset CARGO_TEST_OPTIONS
|
||||
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
|
||||
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
|
||||
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
|
||||
# * strip executable?
|
||||
STRIP="strip" ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; esac;
|
||||
echo set-output name=STRIP::${STRIP}
|
||||
echo ::set-output name=STRIP::${STRIP}
|
||||
- name: Create all needed build/work directories
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p '${{ steps.vars.outputs.STAGING }}'
|
||||
mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
|
||||
- name: rust toolchain ~ install
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
|
||||
target: ${{ matrix.job.target }}
|
||||
override: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: Info
|
||||
shell: bash
|
||||
run: |
|
||||
gcc --version || true
|
||||
rustup -V
|
||||
rustup toolchain list
|
||||
rustup default
|
||||
cargo -V
|
||||
rustc -V
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
|
||||
command: build
|
||||
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}"
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
|
||||
command: test
|
||||
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}"
|
||||
- name: Archive executable artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
|
||||
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: |
|
||||
# binary
|
||||
cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
|
||||
# `strip` binary (if needed)
|
||||
if [ -n "${{ steps.vars.outputs.STRIP }}" ]; then "${{ steps.vars.outputs.STRIP }}" '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' ; fi
|
||||
# README and LICENSE
|
||||
cp README.md '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
|
||||
cp LICENSE '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
|
||||
# base compressed package
|
||||
pushd '${{ steps.vars.outputs.STAGING }}/' >/dev/null
|
||||
case ${{ matrix.job.target }} in
|
||||
*-pc-windows-*) 7z -y a '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* | tail -2 ;;
|
||||
*) tar czf '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* ;;
|
||||
esac;
|
||||
popd >/dev/null
|
||||
- name: Publish
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: steps.vars.outputs.DEPLOY
|
||||
with:
|
||||
files: |
|
||||
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install any prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
case ${{ matrix.job.target }} in
|
||||
arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
|
||||
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install binutils-aarch64-linux-gnu ;;
|
||||
esac
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
# toolchain
|
||||
TOOLCHAIN="stable" ## default to "stable" toolchain
|
||||
# * specify alternate TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: <https://github.com/rust-lang/rust/issues/47048>, <https://github.com/rust-lang/rust/issues/53454>, <https://github.com/rust-lang/cargo/issues/6754>)
|
||||
case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
|
||||
# * use requested TOOLCHAIN if specified
|
||||
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
|
||||
echo set-output name=TOOLCHAIN::${TOOLCHAIN}
|
||||
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
|
||||
# staging directory
|
||||
STAGING='_staging'
|
||||
echo set-output name=STAGING::${STAGING}
|
||||
echo ::set-output name=STAGING::${STAGING}
|
||||
# determine EXE suffix
|
||||
EXE_suffix="" ; case ${{ matrix.job.target }} in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
|
||||
echo set-output name=EXE_suffix::${EXE_suffix}
|
||||
echo ::set-output name=EXE_suffix::${EXE_suffix}
|
||||
# parse commit reference info
|
||||
REF_NAME=${GITHUB_REF#refs/*/}
|
||||
unset REF_BRANCH ; case ${GITHUB_REF} in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac;
|
||||
unset REF_TAG ; case ${GITHUB_REF} in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
|
||||
REF_SHAS=${GITHUB_SHA:0:8}
|
||||
echo set-output name=REF_NAME::${REF_NAME}
|
||||
echo set-output name=REF_BRANCH::${REF_BRANCH}
|
||||
echo set-output name=REF_TAG::${REF_TAG}
|
||||
echo set-output name=REF_SHAS::${REF_SHAS}
|
||||
echo ::set-output name=REF_NAME::${REF_NAME}
|
||||
echo ::set-output name=REF_BRANCH::${REF_BRANCH}
|
||||
echo ::set-output name=REF_TAG::${REF_TAG}
|
||||
echo ::set-output name=REF_SHAS::${REF_SHAS}
|
||||
# parse target
|
||||
unset TARGET_ARCH ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) TARGET_ARCH=arm ;; aarch-*) TARGET_ARCH=aarch64 ;; i686-*) TARGET_ARCH=i686 ;; x86_64-*) TARGET_ARCH=x86_64 ;; esac;
|
||||
echo set-output name=TARGET_ARCH::${TARGET_ARCH}
|
||||
echo ::set-output name=TARGET_ARCH::${TARGET_ARCH}
|
||||
unset TARGET_OS ; case ${{ matrix.job.target }} in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac;
|
||||
echo set-output name=TARGET_OS::${TARGET_OS}
|
||||
echo ::set-output name=TARGET_OS::${TARGET_OS}
|
||||
# package name
|
||||
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
|
||||
PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }}
|
||||
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
|
||||
echo set-output name=PKG_suffix::${PKG_suffix}
|
||||
echo set-output name=PKG_BASENAME::${PKG_BASENAME}
|
||||
echo set-output name=PKG_NAME::${PKG_NAME}
|
||||
echo ::set-output name=PKG_suffix::${PKG_suffix}
|
||||
echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
|
||||
echo ::set-output name=PKG_NAME::${PKG_NAME}
|
||||
# deployable tag? (ie, leading "vM" or "M"; M == version number)
|
||||
unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
|
||||
echo set-output name=DEPLOY::${DEPLOY:-<empty>/false}
|
||||
echo ::set-output name=DEPLOY::${DEPLOY}
|
||||
# target-specific options
|
||||
# * CARGO_FEATURES_OPTION
|
||||
CARGO_FEATURES_OPTION='' ;
|
||||
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
|
||||
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
|
||||
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
|
||||
# * CARGO_USE_CROSS (truthy)
|
||||
CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
|
||||
echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-<empty>/false}
|
||||
echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS}
|
||||
# # * `arm` cannot be tested on ubuntu-* hosts (b/c testing is currently primarily done via comparison of target outputs with built-in outputs and the `arm` target is not executable on the host)
|
||||
JOB_DO_TESTING="true"
|
||||
case ${{ matrix.job.target }} in arm-*|aarch64-*) unset JOB_DO_TESTING ;; esac;
|
||||
echo set-output name=JOB_DO_TESTING::${JOB_DO_TESTING:-<empty>/false}
|
||||
echo ::set-output name=JOB_DO_TESTING::${JOB_DO_TESTING}
|
||||
# # * test only binary for arm-type targets
|
||||
unset CARGO_TEST_OPTIONS
|
||||
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-*|aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
|
||||
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
|
||||
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
|
||||
# * strip executable?
|
||||
STRIP="strip" ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;; aarch64-unknown-linux-musl) STRIP="" ;;esac;
|
||||
echo set-output name=STRIP::${STRIP}
|
||||
echo ::set-output name=STRIP::${STRIP}
|
||||
- name: Create all needed build/work directories
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p '${{ steps.vars.outputs.STAGING }}'
|
||||
mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
|
||||
- name: rust toolchain ~ install
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
|
||||
target: ${{ matrix.job.target }}
|
||||
override: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: Info
|
||||
shell: bash
|
||||
run: |
|
||||
gcc --version || true
|
||||
rustup -V
|
||||
rustup toolchain list
|
||||
rustup default
|
||||
cargo -V
|
||||
rustc -V
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
|
||||
command: build
|
||||
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
||||
- name: Install cargo-deb
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-deb
|
||||
if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl'
|
||||
- name: Build deb
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: deb
|
||||
args: --no-build --target=${{ matrix.job.target }}
|
||||
if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl'
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
|
||||
command: test
|
||||
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
||||
- name: Archive executable artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
|
||||
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
|
||||
- name: Archive deb artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}.deb
|
||||
path: target/${{ matrix.job.target }}/debian
|
||||
if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl'
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: |
|
||||
# binary
|
||||
cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
|
||||
# `strip` binary (if needed)
|
||||
if [ -n "${{ steps.vars.outputs.STRIP }}" ]; then "${{ steps.vars.outputs.STRIP }}" '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' ; fi
|
||||
# README and LICENSE
|
||||
cp README.md '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
|
||||
cp LICENSE '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
|
||||
# base compressed package
|
||||
pushd '${{ steps.vars.outputs.STAGING }}/' >/dev/null
|
||||
case ${{ matrix.job.target }} in
|
||||
*-pc-windows-*) 7z -y a '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* | tail -2 ;;
|
||||
*) tar czf '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* ;;
|
||||
esac;
|
||||
popd >/dev/null
|
||||
- name: Publish
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: steps.vars.outputs.DEPLOY
|
||||
with:
|
||||
files: |
|
||||
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
|
||||
target/${{ matrix.job.target }}/debian/*.deb
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
## fix! [rivy; 2020-22-01] `cargo tarpaulin` is unable to test this repo at the moment; alternate recipe or another testing framework?
|
||||
# coverage:
|
||||
|
||||
83
.travis.yml
83
.travis.yml
@@ -1,83 +0,0 @@
|
||||
# Based on the "trust" template v0.1.2
|
||||
# https://github.com/japaric/trust/tree/v0.1.2
|
||||
|
||||
# ----------- To do a release ---------
|
||||
# tag a commit and push:
|
||||
# git tag v0.4.0.1
|
||||
# git push origin v0.4.0.1
|
||||
# Remember to do a cargo publish to put it in crates.io
|
||||
|
||||
dist: trusty
|
||||
language: rust
|
||||
services: docker
|
||||
sudo: required
|
||||
|
||||
# TODO Rust builds on stable by default, this can be
|
||||
# overridden on a case by case basis down below.
|
||||
|
||||
env:
|
||||
global:
|
||||
# TODO Update this to match the name of your project.
|
||||
- CRATE_NAME=dust
|
||||
|
||||
matrix:
|
||||
# TODO These are all the build jobs. Adjust as necessary. Comment out what you
|
||||
# don't need
|
||||
include:
|
||||
# Linux
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
|
||||
# OSX
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
|
||||
before_install:
|
||||
- set -e
|
||||
- rustup self update
|
||||
|
||||
install:
|
||||
- sh ci/install.sh
|
||||
- source ~/.cargo/env || true
|
||||
|
||||
script:
|
||||
- bash ci/script.sh
|
||||
|
||||
after_script: set +e
|
||||
|
||||
before_deploy:
|
||||
- sh ci/before_deploy.sh
|
||||
|
||||
deploy:
|
||||
# TODO update `api_key.secure`
|
||||
# - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new
|
||||
# - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789
|
||||
# - Paste the output down here
|
||||
# api_key:
|
||||
# secure: UlU73Td7Bkb2N88ws4YGLWR+4U0IMgiou9QQtMnmpouJFjeUNxtLSPMPODVXP7zq4sKt5HR5B3fX9MW4mKm351fvnQEoihETn06pKiXGnY//SlTPTt67MX9ZOYmd9ohJReMDOZDgqhnGLxfymycGtsLAmdjDZnAl+IMqgg0FMyVFj9Cl9aKxnn12lxQyX4zabHKk8TUKD3By8ZoEUnJMHt3gEtOmbDgS4brcTPeHCzqnYFw73LEnkqvz+JP0XwauJY7Cf8lminKm/klmjCkQji8T9SHI52v1g0Fxpx0ucp2o3vulQrLHXaHvZ6Fr7J0cSXXzaFF3rrGLt4t4jU/+9TZm1+n5k5XuPW4x4NTCC9NmIj/z0/z41t82E9qZhzhtm2Jdsg6H2tNk+C774TYqcmR6GCvfRadfjRp3cA5dh0UwDVjH2MJFxlHDVkl6la0mVVRsCGF3oBKZVk0BDl1womfnmI46o/uU+gLknHN6Ed6PHHPPYDViWd3VKdmHKT7XrkMMUF6HjZUtla689DWIOWZSiV++1dVPcl/1TV+6tTmN4bBtPcLuX7SHRuLp2PI2kATvRMECsa7gZRypW4jKpVn7b2yetX9TVI3i1zR5zkQJ3dPg8sATvYPL53aKH/WsqUg4rzoAlbk9so+++R4bQY69LhV3B511B7EAynoZFdM
|
||||
api_key: $API_KEY
|
||||
file_glob: true
|
||||
file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.*
|
||||
on:
|
||||
# TODO Here you can pick which targets will generate binary releases
|
||||
# In this example, there are some targets that are tested using the stable
|
||||
# and nightly channels. This condition makes sure there is only one release
|
||||
# for such targets and that's generated using the stable channel
|
||||
condition: $TRAVIS_RUST_VERSION = stable
|
||||
tags: true
|
||||
provider: releases
|
||||
skip_cleanup: true
|
||||
|
||||
cache: cargo
|
||||
before_cache:
|
||||
# Travis can't cache files that are not readable by "others"
|
||||
- chmod -R a+r $HOME/.cargo
|
||||
|
||||
branches:
|
||||
only:
|
||||
# release tags
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
- master
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
746
Cargo.lock
generated
746
Cargo.lock
generated
@@ -1,585 +1,657 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_cli"
|
||||
version = "0.6.3"
|
||||
name = "assert_cmd"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe"
|
||||
dependencies = [
|
||||
"colored 1.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"environment 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.7"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "c2-chacha"
|
||||
version = "0.2.3"
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.0"
|
||||
version = "3.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "1.9.2"
|
||||
name = "clap_complete"
|
||||
version = "3.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4179da71abd56c26b54dd0c248cc081c1f43b0a1a7e8448e28e57a29baa993d"
|
||||
dependencies = [
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.7.3"
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config-file"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df51e72c150781d2c7d4cbcb0b803277caaa80476786994a62961a8f1010dafb"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.0"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.7.2"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
|
||||
dependencies = [
|
||||
"crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.8.0"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"once_cell",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.0"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "difference"
|
||||
version = "2.0.0"
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "du-dust"
|
||||
version = "0.4.4"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jwalk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term",
|
||||
"assert_cmd",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"config-file",
|
||||
"directories",
|
||||
"lscolors",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"stfu8",
|
||||
"tempfile",
|
||||
"terminal_size",
|
||||
"thousands",
|
||||
"unicode-width",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.5.3"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "environment"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.6"
|
||||
name = "fastrand"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
|
||||
dependencies = [
|
||||
"backtrace 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.6"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.4"
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jwalk"
|
||||
version = "0.4.0"
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.66"
|
||||
version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "lscolors"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.5.3"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.12.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.6"
|
||||
name = "once_cell"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c"
|
||||
dependencies = [
|
||||
"difflib",
|
||||
"itertools",
|
||||
"predicates-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.8"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.2"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.3.0"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
|
||||
dependencies = [
|
||||
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg",
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.7.0"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
|
||||
dependencies = [
|
||||
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.56"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.104"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.44"
|
||||
name = "serde_derive"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
|
||||
dependencies = [
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stfu8"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "019f0c664fd85d5a87dcfb62b40b691055392a35a6e59f4df83d4b770db7e876"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.13"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.1.0"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b"
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.7"
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
name = "thousands"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.3"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
"checksum assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a29ab7c0ed62970beb0534d637a8688842506d0ff9157de83286dacd065c8149"
|
||||
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
"checksum backtrace 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b4b1549d804b6c73f4817df2ba073709e96e426f12987127c48e6745568c350b"
|
||||
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
|
||||
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
||||
"checksum colored 1.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8815e2ab78f3a59928fc32e141fbeece88320a240e43f47b2fd64ea3a88a5b3d"
|
||||
"checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
|
||||
"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
|
||||
"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
|
||||
"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
|
||||
"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
|
||||
"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
|
||||
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
"checksum environment 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f4b14e20978669064c33b4c1e0fb4083412e40fe56cbea2eae80fd7591503ee"
|
||||
"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
|
||||
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
|
||||
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
|
||||
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
||||
"checksum jwalk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3dbf0a8f61baee43a2918ff50ac6a2d3b2c105bc08ed53bc298779f1263409"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
|
||||
"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
|
||||
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
|
||||
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
|
||||
"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
|
||||
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
|
||||
"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
|
||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
|
||||
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
63
Cargo.toml
63
Cargo.toml
@@ -1,9 +1,10 @@
|
||||
[package]
|
||||
name = "du-dust"
|
||||
description = "A more intuitive version of du"
|
||||
version = "0.4.4"
|
||||
version = "0.8.3"
|
||||
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
documentation = "https://github.com/bootandy/dust"
|
||||
homepage = "https://github.com/bootandy/dust"
|
||||
@@ -14,21 +15,67 @@ categories = ["command-line-utilities"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
[badges]
|
||||
travis-ci = {repository = "https://travis-ci.org/bootandy/dust"}
|
||||
travis-ci = { repository = "https://travis-ci.org/bootandy/dust" }
|
||||
|
||||
[[bin]]
|
||||
name = "dust"
|
||||
path = "src/main.rs"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
strip = true
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "=0.12"
|
||||
clap = "=2.33"
|
||||
jwalk = "0.4.0"
|
||||
num_cpus = "1.12"
|
||||
ansi_term = "0.12"
|
||||
clap = "3.2.17"
|
||||
lscolors = "0.7"
|
||||
terminal_size = "0.1"
|
||||
unicode-width = "0.1"
|
||||
rayon = "1"
|
||||
thousands = "0.2"
|
||||
stfu8 = "0.2"
|
||||
regex = "1"
|
||||
config-file = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
directories = "4"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi-util = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cli = "=0.6"
|
||||
assert_cmd = "1"
|
||||
tempfile = "=3"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "3.2.17"
|
||||
clap_complete = "3.2.4"
|
||||
|
||||
[[test]]
|
||||
name = "integration"
|
||||
path = "tests/tests.rs"
|
||||
|
||||
[package.metadata.deb]
|
||||
section = "utils"
|
||||
assets = [
|
||||
[
|
||||
"target/release/dust",
|
||||
"usr/bin/",
|
||||
"755",
|
||||
],
|
||||
[
|
||||
"LICENSE",
|
||||
"usr/share/doc/du-dust/",
|
||||
"644",
|
||||
],
|
||||
[
|
||||
"README.md",
|
||||
"usr/share/doc/du-dust/README",
|
||||
"644",
|
||||
],
|
||||
]
|
||||
extended-description = """\
|
||||
Dust is meant to give you an instant overview of which directories are using
|
||||
disk space without requiring sort or head. Dust will print a maximum of one
|
||||
'Did not have permissions message'.
|
||||
"""
|
||||
|
||||
104
README.md
104
README.md
@@ -1,33 +1,53 @@
|
||||
|
||||
[](https://travis-ci.org/bootandy/dust)
|
||||
|
||||
# Dust
|
||||
|
||||
du + rust = dust. Like du but more intuitive
|
||||
du + rust = dust. Like du but more intuitive.
|
||||
|
||||
# Why
|
||||
|
||||
Because I want an easy way to see where my disk is being used.
|
||||
|
||||
# Demo
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
#### Cargo Install
|
||||
#### Cargo <a href="https://repology.org/project/du-dust/versions"><img src="https://repology.org/badge/vertical-allrepos/du-dust.svg" alt="Packaging status" align="right"></a>
|
||||
|
||||
* cargo install du-dust
|
||||
- `cargo install du-dust`
|
||||
|
||||
#### Download Install
|
||||
#### 🍺 Homebrew (Mac OS)
|
||||
|
||||
* Download linux / mac binary from [Releases](https://github.com/bootandy/dust/releases)
|
||||
* unzip file: tar -xvf _downloaded_file.tar.gz_
|
||||
* move file to executable path: sudo mv dust /usr/local/bin/
|
||||
- `brew install dust`
|
||||
|
||||
#### 🍺 Homebrew (Linux)
|
||||
|
||||
- `brew tap tgotwig/linux-dust && brew install dust`
|
||||
|
||||
#### [Pacstall](https://github.com/pacstall/pacstall) (Debian/Ubuntu)
|
||||
|
||||
- `pacstall -I dust-bin`
|
||||
|
||||
#### Windows:
|
||||
|
||||
- Windows GNU version - works
|
||||
- Windows MSVC - requires: [VCRUNTIME140.dll](https://docs.microsoft.com/en-gb/cpp/windows/latest-supported-vc-redist?view=msvc-170)
|
||||
|
||||
#### Download
|
||||
|
||||
- Download Linux/Mac binary from [Releases](https://github.com/bootandy/dust/releases)
|
||||
- unzip file: `tar -xvf _downloaded_file.tar.gz`
|
||||
- move file to executable path: `sudo mv dust /usr/local/bin/`
|
||||
|
||||
## Overview
|
||||
|
||||
Dust is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust will print a maximum of 1 'Did not have permissions message'.
|
||||
Dust is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust will print a maximum of one 'Did not have permissions message'.
|
||||
|
||||
Dust will list the 20 biggest sub directories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest sub directory will have its size shown in *red*
|
||||
Dust will list 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.
|
||||
|
||||
## Why?
|
||||
|
||||
du has a number of ways of showing you what it finds, in terms of disk consumption, but really, there are only one or two ways you invoke it: with -h for “human readable” units, like 100G or 89k, or with -b for “bytes”. The former is generally used for a quick survey of a directory with a small number of things in it, and the latter for when you have a bunch and need to sort the output numerically, and you’re obligated to either further pass it into something like awk to turn bytes into the appropriate human-friendly unit like mega or gigabytes, or pipe thru sort and head while remembering the '-h' flag. Then once you have the top offenders, you recurse down into the largest one and repeat the process until you’ve found your cruft or gems and can move on.
|
||||
|
||||
Dust assumes that’s what you wanted to do in the first place, and takes care of tracking the largest offenders in terms of actual size, and showing them to you with human-friendly units and in-context within the filetree.
|
||||
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
|
||||
|
||||
@@ -35,42 +55,32 @@ Dust assumes that’s what you wanted to do in the first place, and takes care o
|
||||
Usage: dust
|
||||
Usage: dust <dir>
|
||||
Usage: dust <dir> <another_dir> <and_more>
|
||||
Usage: dust -p <dir> (full-path - does not shorten the path of the subdirectories)
|
||||
Usage: dust -s <dir> (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
|
||||
Usage: dust -n 30 <dir> (Shows 30 directories not 20)
|
||||
Usage: dust -d 3 <dir> (Shows 3 levels of subdirectories)
|
||||
Usage: dust -r <dir> (Reverse order of output, with root at the lowest)
|
||||
Usage: dust -x <dir> (Only show directories on same filesystem)
|
||||
Usage: dust -X ignore <dir> (Ignore all files and directories containing the string 'ignore')
|
||||
```
|
||||
Usage: dust -p (full-path - Show fullpath of the subdirectories)
|
||||
Usage: dust -s (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
|
||||
Usage: dust -n 30 (Shows 30 directories instead of the default [default is terminal height])
|
||||
Usage: dust -d 3 (Shows 3 levels of subdirectories)
|
||||
Usage: dust -r (reverse order of output)
|
||||
Usage: dust -H (si print sizes in powers of 1000 instead of 1024)
|
||||
Usage: dust -X ignore (ignore all files and directories with the name 'ignore')
|
||||
Usage: dust -x (Only show directories on the same filesystem)
|
||||
Usage: dust -b (Do not show percentages or draw ASCII bars)
|
||||
Usage: dust -i (Do not show hidden files)
|
||||
Usage: dust -c (No colors [monochrome])
|
||||
Usage: dust -f (Count files instead of diskspace)
|
||||
Usage: dust -t (Group by filetype)
|
||||
Usage: dust -z 10M (min-size, Only include files larger than 10M)
|
||||
Usage: dust -e regex (Only include files matching this regex (eg dust -e "\.png$" would match png files))
|
||||
Usage: dust -v regex (Exclude files matching this regex (eg dust -v "\.png$" would ignore png files))
|
||||
|
||||
```
|
||||
djin:git/dust> dust
|
||||
1.2G target
|
||||
622M ├─┬ debug
|
||||
445M │ ├── deps
|
||||
70M │ ├── incremental
|
||||
56M │ └── build
|
||||
262M ├─┬ rls
|
||||
262M │ └─┬ debug
|
||||
203M │ ├── deps
|
||||
56M │ └── build
|
||||
165M ├─┬ package
|
||||
165M │ └─┬ du-dust-0.2.4
|
||||
165M │ └─┬ target
|
||||
165M │ └─┬ debug
|
||||
131M │ └── deps
|
||||
165M └─┬ release
|
||||
124M └── deps
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
Dust uses a parallel fetching implementation that greatly improves performance for directory trees with reasonable amount of files (read more than 20) compared to du. This can be as much as 7x faster than du on a clean cache.
|
||||
|
||||
## Alternatives
|
||||
|
||||
* [NCDU](https://dev.yorhel.nl/ncdu)
|
||||
* du -d 1 -h | sort -h
|
||||
- [NCDU](https://dev.yorhel.nl/ncdu)
|
||||
- [dutree](https://github.com/nachoparker/dutree)
|
||||
- [dua](https://github.com/Byron/dua-cli/)
|
||||
- [pdu](https://github.com/KSXGitHub/parallel-disk-usage)
|
||||
- [dirstat-rs](https://github.com/scullionw/dirstat-rs)
|
||||
- du -d 1 -h | sort -h
|
||||
|
||||
Note: Apparent-size is calculated slightly differently in dust to gdu. In dust each hard link is counted as using file_length space. In gdu only the first entry is counted.
|
||||
|
||||
18
build.rs
Normal file
18
build.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use clap_complete::{generate_to, shells::*};
|
||||
use std::io::Error;
|
||||
|
||||
include!("src/cli.rs");
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let outdir = "completions";
|
||||
let app_name = "dust";
|
||||
let mut cmd = build_cli();
|
||||
|
||||
generate_to(Bash, &mut cmd, app_name, outdir)?;
|
||||
generate_to(Zsh, &mut cmd, app_name, outdir)?;
|
||||
generate_to(Fish, &mut cmd, app_name, outdir)?;
|
||||
generate_to(PowerShell, &mut cmd, app_name, outdir)?;
|
||||
generate_to(Elvish, &mut cmd, app_name, outdir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
10
ci/how2publish.txt
Normal file
10
ci/how2publish.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# ----------- To do a release ---------
|
||||
# edit version in cargo.toml
|
||||
# tag a commit and push (increment version in Cargo.toml first):
|
||||
# git tag v0.4.5
|
||||
# git push origin v0.4.5
|
||||
|
||||
# cargo publish to put it in crates.io
|
||||
|
||||
# To install locally [Do before pushing it]
|
||||
#cargo install --path .
|
||||
67
completions/_dust
Normal file
67
completions/_dust
Normal file
@@ -0,0 +1,67 @@
|
||||
#compdef dust
|
||||
|
||||
autoload -U is-at-least
|
||||
|
||||
_dust() {
|
||||
typeset -A opt_args
|
||||
typeset -a _arguments_options
|
||||
local ret=1
|
||||
|
||||
if is-at-least 5.2; then
|
||||
_arguments_options=(-s -S -C)
|
||||
else
|
||||
_arguments_options=(-s -C)
|
||||
fi
|
||||
|
||||
local context curcontext="$curcontext" state line
|
||||
_arguments "${_arguments_options[@]}" \
|
||||
'-d+[Depth to show]: : ' \
|
||||
'--depth=[Depth to show]: : ' \
|
||||
'-n+[Number of lines of output to show. (Default is terminal_height - 10)]: : ' \
|
||||
'--number-of-lines=[Number of lines of output to show. (Default is terminal_height - 10)]: : ' \
|
||||
'*-X+[Exclude any file or directory with this name]: : ' \
|
||||
'*--ignore-directory=[Exclude any file or directory with this name]: : ' \
|
||||
'-z+[Minimum size file to include in output]: : ' \
|
||||
'--min-size=[Minimum size file to include in output]: : ' \
|
||||
'(-e --filter -t --file_types)*-v+[Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ]: : ' \
|
||||
'(-e --filter -t --file_types)*--invert-filter=[Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ]: : ' \
|
||||
'(-t --file_types)*-e+[Only include filepaths matching this regex. For png files type: -e "\\.png$" ]: : ' \
|
||||
'(-t --file_types)*--filter=[Only include filepaths matching this regex. For png files type: -e "\\.png$" ]: : ' \
|
||||
'-w+[Specify width of output overriding the auto detection of terminal width]: : ' \
|
||||
'--terminal_width=[Specify width of output overriding the auto detection of terminal width]: : ' \
|
||||
'-h[Print help information]' \
|
||||
'--help[Print help information]' \
|
||||
'-V[Print version information]' \
|
||||
'--version[Print version information]' \
|
||||
'-p[Subdirectories will not have their path shortened]' \
|
||||
'--full-paths[Subdirectories will not have their path shortened]' \
|
||||
'-x[Only count the files and directories on the same filesystem as the supplied directory]' \
|
||||
'--limit-filesystem[Only count the files and directories on the same filesystem as the supplied directory]' \
|
||||
'-s[Use file length instead of blocks]' \
|
||||
'--apparent-size[Use file length instead of blocks]' \
|
||||
'-r[Print tree upside down (biggest highest)]' \
|
||||
'--reverse[Print tree upside down (biggest highest)]' \
|
||||
'-c[No colors will be printed (Useful for commands like: watch)]' \
|
||||
'--no-colors[No colors will be printed (Useful for commands like: watch)]' \
|
||||
'-b[No percent bars or percentages will be displayed]' \
|
||||
'--no-percent-bars[No percent bars or percentages will be displayed]' \
|
||||
'--skip-total[No total row will be displayed]' \
|
||||
'-f[Directory '\''size'\'' is number of child files/dirs not disk size]' \
|
||||
'--filecount[Directory '\''size'\'' is number of child files/dirs not disk size]' \
|
||||
'-i[Do not display hidden files]' \
|
||||
'--ignore_hidden[Do not display hidden files]' \
|
||||
'(-d --depth)-t[show only these file types]' \
|
||||
'(-d --depth)--file_types[show only these file types]' \
|
||||
'-H[print sizes in powers of 1000 (e.g., 1.1G)]' \
|
||||
'--si[print sizes in powers of 1000 (e.g., 1.1G)]' \
|
||||
'*::inputs:' \
|
||||
&& ret=0
|
||||
}
|
||||
|
||||
(( $+functions[_dust_commands] )) ||
|
||||
_dust_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'dust commands' commands "$@"
|
||||
}
|
||||
|
||||
_dust "$@"
|
||||
69
completions/_dust.ps1
Normal file
69
completions/_dust.ps1
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
using namespace System.Management.Automation
|
||||
using namespace System.Management.Automation.Language
|
||||
|
||||
Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
|
||||
$commandElements = $commandAst.CommandElements
|
||||
$command = @(
|
||||
'dust'
|
||||
for ($i = 1; $i -lt $commandElements.Count; $i++) {
|
||||
$element = $commandElements[$i]
|
||||
if ($element -isnot [StringConstantExpressionAst] -or
|
||||
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
||||
$element.Value.StartsWith('-') -or
|
||||
$element.Value -eq $wordToComplete) {
|
||||
break
|
||||
}
|
||||
$element.Value
|
||||
}) -join ';'
|
||||
|
||||
$completions = @(switch ($command) {
|
||||
'dust' {
|
||||
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Depth to show')
|
||||
[CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Depth to show')
|
||||
[CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)')
|
||||
[CompletionResult]::new('--number-of-lines', 'number-of-lines', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)')
|
||||
[CompletionResult]::new('-X', 'X', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this name')
|
||||
[CompletionResult]::new('--ignore-directory', 'ignore-directory', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this name')
|
||||
[CompletionResult]::new('-z', 'z', [CompletionResultType]::ParameterName, 'Minimum size file to include in output')
|
||||
[CompletionResult]::new('--min-size', 'min-size', [CompletionResultType]::ParameterName, 'Minimum size file to include in output')
|
||||
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
|
||||
[CompletionResult]::new('--invert-filter', 'invert-filter', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
|
||||
[CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$" ')
|
||||
[CompletionResult]::new('--filter', 'filter', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$" ')
|
||||
[CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
|
||||
[CompletionResult]::new('--terminal_width', 'terminal_width', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
|
||||
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
|
||||
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
|
||||
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened')
|
||||
[CompletionResult]::new('--full-paths', 'full-paths', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened')
|
||||
[CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory')
|
||||
[CompletionResult]::new('--limit-filesystem', 'limit-filesystem', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory')
|
||||
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Use file length instead of blocks')
|
||||
[CompletionResult]::new('--apparent-size', 'apparent-size', [CompletionResultType]::ParameterName, 'Use file length instead of blocks')
|
||||
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)')
|
||||
[CompletionResult]::new('--reverse', 'reverse', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)')
|
||||
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
|
||||
[CompletionResult]::new('--no-colors', 'no-colors', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
|
||||
[CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed')
|
||||
[CompletionResult]::new('--no-percent-bars', 'no-percent-bars', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed')
|
||||
[CompletionResult]::new('--skip-total', 'skip-total', [CompletionResultType]::ParameterName, 'No total row will be displayed')
|
||||
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files/dirs not disk size')
|
||||
[CompletionResult]::new('--filecount', 'filecount', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files/dirs not disk size')
|
||||
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Do not display hidden files')
|
||||
[CompletionResult]::new('--ignore_hidden', 'ignore_hidden', [CompletionResultType]::ParameterName, 'Do not display hidden files')
|
||||
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'show only these file types')
|
||||
[CompletionResult]::new('--file_types', 'file_types', [CompletionResultType]::ParameterName, 'show only these file types')
|
||||
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'print sizes in powers of 1000 (e.g., 1.1G)')
|
||||
[CompletionResult]::new('--si', 'si', [CompletionResultType]::ParameterName, 'print sizes in powers of 1000 (e.g., 1.1G)')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
||||
Sort-Object -Property ListItemText
|
||||
}
|
||||
94
completions/dust.bash
Normal file
94
completions/dust.bash
Normal file
@@ -0,0 +1,94 @@
|
||||
_dust() {
|
||||
local i cur prev opts cmds
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
cmd=""
|
||||
opts=""
|
||||
|
||||
for i in ${COMP_WORDS[@]}
|
||||
do
|
||||
case "${i}" in
|
||||
"$1")
|
||||
cmd="dust"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "${cmd}" in
|
||||
dust)
|
||||
opts="-h -V -d -n -p -X -x -s -r -c -b -z -f -i -v -e -t -w -H --help --version --depth --number-of-lines --full-paths --ignore-directory --limit-filesystem --apparent-size --reverse --no-colors --no-percent-bars --min-size --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --si <inputs>..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
--depth)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-d)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--number-of-lines)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-n)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--ignore-directory)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-X)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--min-size)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-z)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--invert-filter)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-v)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--filter)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-e)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--terminal_width)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-w)
|
||||
COMPREPLY=($(compgen -f "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _dust -o bashdefault -o default dust
|
||||
63
completions/dust.elv
Normal file
63
completions/dust.elv
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
use builtin;
|
||||
use str;
|
||||
|
||||
set edit:completion:arg-completer[dust] = {|@words|
|
||||
fn spaces {|n|
|
||||
builtin:repeat $n ' ' | str:join ''
|
||||
}
|
||||
fn cand {|text desc|
|
||||
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
|
||||
}
|
||||
var command = 'dust'
|
||||
for word $words[1..-1] {
|
||||
if (str:has-prefix $word '-') {
|
||||
break
|
||||
}
|
||||
set command = $command';'$word
|
||||
}
|
||||
var completions = [
|
||||
&'dust'= {
|
||||
cand -d 'Depth to show'
|
||||
cand --depth 'Depth to show'
|
||||
cand -n 'Number of lines of output to show. (Default is terminal_height - 10)'
|
||||
cand --number-of-lines 'Number of lines of output to show. (Default is terminal_height - 10)'
|
||||
cand -X 'Exclude any file or directory with this name'
|
||||
cand --ignore-directory 'Exclude any file or directory with this name'
|
||||
cand -z 'Minimum size file to include in output'
|
||||
cand --min-size 'Minimum size file to include in output'
|
||||
cand -v 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" '
|
||||
cand --invert-filter 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" '
|
||||
cand -e 'Only include filepaths matching this regex. For png files type: -e "\.png$" '
|
||||
cand --filter 'Only include filepaths matching this regex. For png files type: -e "\.png$" '
|
||||
cand -w 'Specify width of output overriding the auto detection of terminal width'
|
||||
cand --terminal_width 'Specify width of output overriding the auto detection of terminal width'
|
||||
cand -h 'Print help information'
|
||||
cand --help 'Print help information'
|
||||
cand -V 'Print version information'
|
||||
cand --version 'Print version information'
|
||||
cand -p 'Subdirectories will not have their path shortened'
|
||||
cand --full-paths 'Subdirectories will not have their path shortened'
|
||||
cand -x 'Only count the files and directories on the same filesystem as the supplied directory'
|
||||
cand --limit-filesystem 'Only count the files and directories on the same filesystem as the supplied directory'
|
||||
cand -s 'Use file length instead of blocks'
|
||||
cand --apparent-size 'Use file length instead of blocks'
|
||||
cand -r 'Print tree upside down (biggest highest)'
|
||||
cand --reverse 'Print tree upside down (biggest highest)'
|
||||
cand -c 'No colors will be printed (Useful for commands like: watch)'
|
||||
cand --no-colors 'No colors will be printed (Useful for commands like: watch)'
|
||||
cand -b 'No percent bars or percentages will be displayed'
|
||||
cand --no-percent-bars 'No percent bars or percentages will be displayed'
|
||||
cand --skip-total 'No total row will be displayed'
|
||||
cand -f 'Directory ''size'' is number of child files/dirs not disk size'
|
||||
cand --filecount 'Directory ''size'' is number of child files/dirs not disk size'
|
||||
cand -i 'Do not display hidden files'
|
||||
cand --ignore_hidden 'Do not display hidden files'
|
||||
cand -t 'show only these file types'
|
||||
cand --file_types 'show only these file types'
|
||||
cand -H 'print sizes in powers of 1000 (e.g., 1.1G)'
|
||||
cand --si 'print sizes in powers of 1000 (e.g., 1.1G)'
|
||||
}
|
||||
]
|
||||
$completions[$command]
|
||||
}
|
||||
20
completions/dust.fish
Normal file
20
completions/dust.fish
Normal file
@@ -0,0 +1,20 @@
|
||||
complete -c dust -s d -l depth -d 'Depth to show' -r
|
||||
complete -c dust -s n -l number-of-lines -d 'Number of lines of output to show. (Default is terminal_height - 10)' -r
|
||||
complete -c dust -s X -l ignore-directory -d 'Exclude any file or directory with this name' -r
|
||||
complete -c dust -s z -l min-size -d 'Minimum size file to include in output' -r
|
||||
complete -c dust -s v -l invert-filter -d 'Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ' -r
|
||||
complete -c dust -s e -l filter -d 'Only include filepaths matching this regex. For png files type: -e "\\.png$" ' -r
|
||||
complete -c dust -s w -l terminal_width -d 'Specify width of output overriding the auto detection of terminal width' -r
|
||||
complete -c dust -s h -l help -d 'Print help information'
|
||||
complete -c dust -s V -l version -d 'Print version information'
|
||||
complete -c dust -s p -l full-paths -d 'Subdirectories will not have their path shortened'
|
||||
complete -c dust -s x -l limit-filesystem -d 'Only count the files and directories on the same filesystem as the supplied directory'
|
||||
complete -c dust -s s -l apparent-size -d 'Use file length instead of blocks'
|
||||
complete -c dust -s r -l reverse -d 'Print tree upside down (biggest highest)'
|
||||
complete -c dust -s c -l no-colors -d 'No colors will be printed (Useful for commands like: watch)'
|
||||
complete -c dust -s b -l no-percent-bars -d 'No percent bars or percentages will be displayed'
|
||||
complete -c dust -l skip-total -d 'No total row will be displayed'
|
||||
complete -c dust -s f -l filecount -d 'Directory \'size\' is number of child files/dirs not disk size'
|
||||
complete -c dust -s i -l ignore_hidden -d 'Do not display hidden files'
|
||||
complete -c dust -s t -l file_types -d 'show only these file types'
|
||||
complete -c dust -s H -l si -d 'print sizes in powers of 1000 (e.g., 1.1G)'
|
||||
13
config/config.toml
Normal file
13
config/config.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Sample Config file, works with toml and yaml
|
||||
# Place in either:
|
||||
# ~/.config/dust/config.toml
|
||||
# ~/.dust.toml
|
||||
|
||||
reverse=true
|
||||
display-full-paths=true
|
||||
display-apparent-size=true
|
||||
no-colors=true
|
||||
no-bars=true
|
||||
skip-total=true
|
||||
ignore-hidden=true
|
||||
iso=true
|
||||
BIN
media/snap.png
Normal file
BIN
media/snap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
135
src/cli.rs
Normal file
135
src/cli.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use clap::{Arg, Command};
|
||||
|
||||
pub fn build_cli() -> Command<'static> {
|
||||
Command::new("Dust")
|
||||
.about("Like du but more intuitive")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.trailing_var_arg(true)
|
||||
.arg(
|
||||
Arg::new("depth")
|
||||
.short('d')
|
||||
.long("depth")
|
||||
.help("Depth to show")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("number_of_lines")
|
||||
.short('n')
|
||||
.long("number-of-lines")
|
||||
.help("Number of lines of output to show. (Default is terminal_height - 10)")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("display_full_paths")
|
||||
.short('p')
|
||||
.long("full-paths")
|
||||
.help("Subdirectories will not have their path shortened"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ignore_directory")
|
||||
.short('X')
|
||||
.long("ignore-directory")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.multiple_occurrences(true)
|
||||
.help("Exclude any file or directory with this name"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("limit_filesystem")
|
||||
.short('x')
|
||||
.long("limit-filesystem")
|
||||
.help("Only count the files and directories on the same filesystem as the supplied directory"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("display_apparent_size")
|
||||
.short('s')
|
||||
.long("apparent-size")
|
||||
.help("Use file length instead of blocks"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("reverse")
|
||||
.short('r')
|
||||
.long("reverse")
|
||||
.help("Print tree upside down (biggest highest)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no_colors")
|
||||
.short('c')
|
||||
.long("no-colors")
|
||||
.help("No colors will be printed (Useful for commands like: watch)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no_bars")
|
||||
.short('b')
|
||||
.long("no-percent-bars")
|
||||
.help("No percent bars or percentages will be displayed"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("min_size")
|
||||
.short('z')
|
||||
.long("min-size")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.help("Minimum size file to include in output"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("skip_total")
|
||||
.long("skip-total")
|
||||
.help("No total row will be displayed"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("by_filecount")
|
||||
.short('f')
|
||||
.long("filecount")
|
||||
.help("Directory 'size' is number of child files/dirs not disk size"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ignore_hidden")
|
||||
.short('i') // Do not use 'h' this is used by 'help'
|
||||
.long("ignore_hidden")
|
||||
.help("Do not display hidden files"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("invert_filter")
|
||||
.short('v')
|
||||
.long("invert-filter")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.multiple_occurrences(true)
|
||||
.conflicts_with("filter")
|
||||
.conflicts_with("types")
|
||||
.help("Exclude filepaths matching this regex. To ignore png files type: -v \"\\.png$\" "),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("filter")
|
||||
.short('e')
|
||||
.long("filter")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.multiple_occurrences(true)
|
||||
.conflicts_with("types")
|
||||
.help("Only include filepaths matching this regex. For png files type: -e \"\\.png$\" "),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("types")
|
||||
.short('t')
|
||||
.long("file_types")
|
||||
.conflicts_with("depth")
|
||||
.help("show only these file types"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("width")
|
||||
.short('w')
|
||||
.long("terminal_width")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.help("Specify width of output overriding the auto detection of terminal width"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("iso")
|
||||
.short('H')
|
||||
.long("si")
|
||||
.help("print sizes in powers of 1000 (e.g., 1.1G)")
|
||||
)
|
||||
.arg(Arg::new("inputs").multiple_occurrences(true).default_value("."))
|
||||
}
|
||||
148
src/config.rs
Normal file
148
src/config.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use clap::ArgMatches;
|
||||
use config_file::FromConfigFile;
|
||||
use serde::Deserialize;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::display::UNITS;
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
pub display_full_paths: Option<bool>,
|
||||
pub display_apparent_size: Option<bool>,
|
||||
pub reverse: Option<bool>,
|
||||
pub no_colors: Option<bool>,
|
||||
pub no_bars: Option<bool>,
|
||||
pub skip_total: Option<bool>,
|
||||
pub ignore_hidden: Option<bool>,
|
||||
pub iso: Option<bool>,
|
||||
pub min_size: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn get_no_colors(&self, options: &ArgMatches) -> bool {
|
||||
Some(true) == self.no_colors || options.is_present("no_colors")
|
||||
}
|
||||
pub fn get_apparent_size(&self, options: &ArgMatches) -> bool {
|
||||
Some(true) == self.display_apparent_size || options.is_present("display_apparent_size")
|
||||
}
|
||||
pub fn get_ignore_hidden(&self, options: &ArgMatches) -> bool {
|
||||
Some(true) == self.ignore_hidden || options.is_present("ignore_hidden")
|
||||
}
|
||||
pub fn get_full_paths(&self, options: &ArgMatches) -> bool {
|
||||
Some(true) == self.display_full_paths || options.is_present("display_full_paths")
|
||||
}
|
||||
pub fn get_reverse(&self, options: &ArgMatches) -> bool {
|
||||
Some(true) == self.reverse || options.is_present("reverse")
|
||||
}
|
||||
pub fn get_no_bars(&self, options: &ArgMatches) -> bool {
|
||||
Some(true) == self.no_bars || options.is_present("no_bars")
|
||||
}
|
||||
pub fn get_iso(&self, options: &ArgMatches) -> bool {
|
||||
Some(true) == self.iso || options.is_present("iso")
|
||||
}
|
||||
pub fn get_skip_total(&self, options: &ArgMatches) -> bool {
|
||||
Some(true) == self.skip_total || options.is_present("skip_total")
|
||||
}
|
||||
pub fn get_min_size(&self, options: &ArgMatches, iso: bool) -> Option<usize> {
|
||||
let size_from_param = options.value_of("min_size");
|
||||
self._get_min_size(size_from_param, iso)
|
||||
}
|
||||
fn _get_min_size(&self, min_size: Option<&str>, iso: bool) -> Option<usize> {
|
||||
let size_from_param = min_size.and_then(|a| convert_min_size(a, iso));
|
||||
|
||||
if size_from_param.is_none() {
|
||||
self.min_size
|
||||
.as_ref()
|
||||
.and_then(|a| convert_min_size(a.as_ref(), iso))
|
||||
} else {
|
||||
size_from_param
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_min_size(input: &str, iso: bool) -> Option<usize> {
|
||||
let chars_as_vec: Vec<char> = input.chars().collect();
|
||||
match chars_as_vec.split_last() {
|
||||
Some((last, start)) => {
|
||||
let mut starts: String = start.iter().collect::<String>();
|
||||
|
||||
for (i, u) in UNITS.iter().rev().enumerate() {
|
||||
if Some(*u) == last.to_uppercase().next() {
|
||||
return match starts.parse::<usize>() {
|
||||
Ok(pure) => {
|
||||
let num: usize = if iso { 1000 } else { 1024 };
|
||||
let marker = pure * num.pow((i + 1) as u32);
|
||||
Some(marker)
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("Ignoring invalid min-size: {}", input);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
starts.push(*last);
|
||||
starts
|
||||
.parse()
|
||||
.map_err(|_| {
|
||||
eprintln!("Ignoring invalid min-size: {}", input);
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config_locations(base: &Path) -> Vec<PathBuf> {
|
||||
vec![
|
||||
base.join(".dust.toml"),
|
||||
base.join(".config").join("dust").join("config.toml"),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_config() -> Config {
|
||||
if let Some(home) = directories::BaseDirs::new() {
|
||||
for path in get_config_locations(home.home_dir()) {
|
||||
if path.exists() {
|
||||
if let Ok(config) = Config::from_config_file(path) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Config {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_conversion() {
|
||||
assert_eq!(convert_min_size("55", false), Some(55));
|
||||
assert_eq!(convert_min_size("12344321", false), Some(12344321));
|
||||
assert_eq!(convert_min_size("95RUBBISH", false), None);
|
||||
assert_eq!(convert_min_size("10K", false), Some(10 * 1024));
|
||||
assert_eq!(convert_min_size("10M", false), Some(10 * 1024usize.pow(2)));
|
||||
assert_eq!(convert_min_size("10M", true), Some(10 * 1000usize.pow(2)));
|
||||
assert_eq!(convert_min_size("2G", false), Some(2 * 1024usize.pow(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_min_size_from_config_applied_or_overridden() {
|
||||
let c = Config {
|
||||
min_size: Some("1K".to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(c._get_min_size(None, false), Some(1024));
|
||||
assert_eq!(c._get_min_size(Some("2K"), false), Some(2048));
|
||||
|
||||
assert_eq!(c._get_min_size(None, true), Some(1000));
|
||||
assert_eq!(c._get_min_size(Some("2K"), true), Some(2000));
|
||||
}
|
||||
}
|
||||
226
src/dir_walker.rs
Normal file
226
src/dir_walker.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
use std::fs;
|
||||
|
||||
use crate::node::Node;
|
||||
use crate::utils::is_filtered_out_due_to_invert_regex;
|
||||
use crate::utils::is_filtered_out_due_to_regex;
|
||||
use rayon::iter::ParallelBridge;
|
||||
use rayon::prelude::ParallelIterator;
|
||||
use regex::Regex;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::sync::atomic;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::node::build_node;
|
||||
use std::fs::DirEntry;
|
||||
|
||||
use crate::platform::get_metadata;
|
||||
|
||||
pub struct WalkData<'a> {
|
||||
pub ignore_directories: HashSet<PathBuf>,
|
||||
pub filter_regex: &'a [Regex],
|
||||
pub invert_filter_regex: &'a [Regex],
|
||||
pub allowed_filesystems: HashSet<u64>,
|
||||
pub use_apparent_size: bool,
|
||||
pub by_filecount: bool,
|
||||
pub ignore_hidden: bool,
|
||||
}
|
||||
|
||||
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> (Vec<Node>, bool) {
|
||||
let permissions_flag = AtomicBool::new(false);
|
||||
|
||||
let top_level_nodes: Vec<_> = dirs
|
||||
.into_iter()
|
||||
.filter_map(|d| {
|
||||
clean_inodes(
|
||||
walk(d, &permissions_flag, &walk_data, 0)?,
|
||||
&mut HashSet::new(),
|
||||
walk_data.use_apparent_size,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
(top_level_nodes, permissions_flag.into_inner())
|
||||
}
|
||||
|
||||
// Remove files which have the same inode, we don't want to double count them.
|
||||
fn clean_inodes(
|
||||
x: Node,
|
||||
inodes: &mut HashSet<(u64, u64)>,
|
||||
use_apparent_size: bool,
|
||||
) -> Option<Node> {
|
||||
if !use_apparent_size {
|
||||
if let Some(id) = x.inode_device {
|
||||
if !inodes.insert(id) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort Nodes so iteration order is predictable
|
||||
let mut tmp: Vec<_> = x.children;
|
||||
tmp.sort_by(sort_by_inode);
|
||||
let new_children: Vec<_> = tmp
|
||||
.into_iter()
|
||||
.filter_map(|c| clean_inodes(c, inodes, use_apparent_size))
|
||||
.collect();
|
||||
|
||||
Some(Node {
|
||||
name: x.name,
|
||||
size: x.size + new_children.iter().map(|c| c.size).sum::<u64>(),
|
||||
children: new_children,
|
||||
inode_device: x.inode_device,
|
||||
depth: x.depth,
|
||||
})
|
||||
}
|
||||
|
||||
fn sort_by_inode(a: &Node, b: &Node) -> std::cmp::Ordering {
|
||||
// Sorting by inode is quicker than by sorting by name/size
|
||||
if let Some(x) = a.inode_device {
|
||||
if let Some(y) = b.inode_device {
|
||||
if x.0 != y.0 {
|
||||
return x.0.cmp(&y.0);
|
||||
} else if x.1 != y.1 {
|
||||
return x.1.cmp(&y.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
a.name.cmp(&b.name)
|
||||
}
|
||||
|
||||
fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
|
||||
let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.');
|
||||
let is_ignored_path = walk_data.ignore_directories.contains(&entry.path());
|
||||
|
||||
if !walk_data.allowed_filesystems.is_empty() {
|
||||
let size_inode_device = get_metadata(&entry.path(), false);
|
||||
|
||||
if let Some((_size, Some((_id, dev)))) = size_inode_device {
|
||||
if !walk_data.allowed_filesystems.contains(&dev) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keeping `walk_data.filter_regex.is_empty()` is important for performance reasons, it stops unnecessary work
|
||||
if !walk_data.filter_regex.is_empty()
|
||||
&& entry.path().is_file()
|
||||
&& is_filtered_out_due_to_regex(walk_data.filter_regex, &entry.path())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if !walk_data.invert_filter_regex.is_empty()
|
||||
&& entry.path().is_file()
|
||||
&& is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &entry.path())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
(is_dot_file && walk_data.ignore_hidden) || is_ignored_path
|
||||
}
|
||||
|
||||
fn walk(
|
||||
dir: PathBuf,
|
||||
permissions_flag: &AtomicBool,
|
||||
walk_data: &WalkData,
|
||||
depth: usize,
|
||||
) -> Option<Node> {
|
||||
let mut children = vec![];
|
||||
|
||||
if let Ok(entries) = fs::read_dir(&dir) {
|
||||
children = entries
|
||||
.into_iter()
|
||||
.par_bridge()
|
||||
.filter_map(|entry| {
|
||||
if let Ok(ref entry) = entry {
|
||||
// uncommenting the below line gives simpler code but
|
||||
// rayon doesn't parallelize as well giving a 3X performance drop
|
||||
// hence we unravel the recursion a bit
|
||||
|
||||
// return walk(entry.path(), permissions_flag, ignore_directories, allowed_filesystems, use_apparent_size, by_filecount, ignore_hidden);
|
||||
|
||||
if !ignore_file(entry, walk_data) {
|
||||
if let Ok(data) = entry.file_type() {
|
||||
return if data.is_dir() && !data.is_symlink() {
|
||||
walk(entry.path(), permissions_flag, walk_data, depth + 1)
|
||||
} else {
|
||||
build_node(
|
||||
entry.path(),
|
||||
vec![],
|
||||
walk_data.filter_regex,
|
||||
walk_data.invert_filter_regex,
|
||||
walk_data.use_apparent_size,
|
||||
data.is_symlink(),
|
||||
data.is_file(),
|
||||
walk_data.by_filecount,
|
||||
depth,
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
permissions_flag.store(true, atomic::Ordering::Relaxed);
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
} else {
|
||||
// Handle edge case where dust is called with a file instead of a directory
|
||||
if !dir.exists() {
|
||||
permissions_flag.store(true, atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
build_node(
|
||||
dir,
|
||||
children,
|
||||
walk_data.filter_regex,
|
||||
walk_data.invert_filter_regex,
|
||||
walk_data.use_apparent_size,
|
||||
false,
|
||||
false,
|
||||
walk_data.by_filecount,
|
||||
depth,
|
||||
)
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[cfg(test)]
|
||||
fn create_node() -> Node {
|
||||
Node {
|
||||
name: PathBuf::new(),
|
||||
size: 10,
|
||||
children: vec![],
|
||||
inode_device: Some((5, 6)),
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn test_should_ignore_file() {
|
||||
let mut inodes = HashSet::new();
|
||||
let n = create_node();
|
||||
|
||||
// First time we insert the node
|
||||
assert_eq!(clean_inodes(n.clone(), &mut inodes, false), Some(n.clone()));
|
||||
|
||||
// Second time is a duplicate - we ignore it
|
||||
assert_eq!(clean_inodes(n.clone(), &mut inodes, false), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn test_should_not_ignore_files_if_using_apparent_size() {
|
||||
let mut inodes = HashSet::new();
|
||||
let n = create_node();
|
||||
|
||||
// If using apparent size we include Nodes, even if duplicate inodes
|
||||
assert_eq!(clean_inodes(n.clone(), &mut inodes, true), Some(n.clone()));
|
||||
assert_eq!(clean_inodes(n.clone(), &mut inodes, true), Some(n.clone()));
|
||||
}
|
||||
}
|
||||
501
src/display.rs
501
src/display.rs
@@ -1,121 +1,243 @@
|
||||
extern crate ansi_term;
|
||||
use crate::display_node::DisplayNode;
|
||||
|
||||
use self::ansi_term::Colour::Fixed;
|
||||
use self::ansi_term::Style;
|
||||
use crate::utils::Node;
|
||||
use ansi_term::Colour::Red;
|
||||
use lscolors::{LsColors, Style};
|
||||
|
||||
static UNITS: [char; 4] = ['T', 'G', 'M', 'K'];
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use stfu8::encode_u8;
|
||||
|
||||
use std::cmp::max;
|
||||
use std::cmp::min;
|
||||
use std::fs;
|
||||
use std::iter::repeat;
|
||||
use std::path::Path;
|
||||
use thousands::Separable;
|
||||
|
||||
pub static UNITS: [char; 4] = ['T', 'G', 'M', 'K'];
|
||||
static BLOCKS: [char; 5] = ['█', '▓', '▒', '░', ' '];
|
||||
|
||||
pub struct DisplayData {
|
||||
pub short_paths: bool,
|
||||
pub is_reversed: bool,
|
||||
pub colors_on: bool,
|
||||
pub by_filecount: bool,
|
||||
pub num_chars_needed_on_left_most: usize,
|
||||
pub base_size: u64,
|
||||
pub longest_string_length: usize,
|
||||
pub ls_colors: LsColors,
|
||||
pub iso: bool,
|
||||
}
|
||||
|
||||
impl DisplayData {
|
||||
fn get_first_chars(&self) -> &str {
|
||||
if self.is_reversed {
|
||||
"─┴"
|
||||
} else {
|
||||
"─┬"
|
||||
fn get_tree_chars(&self, was_i_last: bool, has_children: bool) -> &'static str {
|
||||
match (self.is_reversed, was_i_last, has_children) {
|
||||
(true, true, true) => "┌─┴",
|
||||
(true, true, false) => "┌──",
|
||||
(true, false, true) => "├─┴",
|
||||
(true, false, false) => "├──",
|
||||
(false, true, true) => "└─┬",
|
||||
(false, true, false) => "└──",
|
||||
(false, false, true) => "├─┬",
|
||||
(false, false, false) => "├──",
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_if)]
|
||||
fn get_tree_chars(
|
||||
&self,
|
||||
num_siblings: u64,
|
||||
max_siblings: u64,
|
||||
has_children: bool,
|
||||
) -> &'static str {
|
||||
fn is_biggest(&self, num_siblings: usize, max_siblings: u64) -> bool {
|
||||
if self.is_reversed {
|
||||
if num_siblings == max_siblings - 1 {
|
||||
if has_children {
|
||||
"┌─┴"
|
||||
} else {
|
||||
"┌──"
|
||||
}
|
||||
} else if has_children {
|
||||
"├─┴"
|
||||
} else {
|
||||
"├──"
|
||||
}
|
||||
num_siblings == (max_siblings - 1) as usize
|
||||
} else {
|
||||
if num_siblings == 0 {
|
||||
if has_children {
|
||||
"└─┬"
|
||||
} else {
|
||||
"└──"
|
||||
}
|
||||
} else if has_children {
|
||||
"├─┬"
|
||||
} else {
|
||||
"├──"
|
||||
}
|
||||
num_siblings == 0
|
||||
}
|
||||
}
|
||||
|
||||
fn is_biggest(&self, num_siblings: u64, max_siblings: u64) -> bool {
|
||||
fn is_last(&self, num_siblings: usize, max_siblings: u64) -> bool {
|
||||
if self.is_reversed {
|
||||
num_siblings == 0
|
||||
} else {
|
||||
num_siblings == max_siblings - 1
|
||||
num_siblings == (max_siblings - 1) as usize
|
||||
}
|
||||
}
|
||||
|
||||
fn get_children_from_node(&self, node: Node) -> impl Iterator<Item = Node> {
|
||||
if self.is_reversed {
|
||||
let n: Vec<Node> = node.children.into_iter().rev().map(|a| a).collect();
|
||||
n.into_iter()
|
||||
fn percent_size(&self, node: &DisplayNode) -> f32 {
|
||||
let result = node.size as f32 / self.base_size as f32;
|
||||
if result.is_normal() {
|
||||
result
|
||||
} else {
|
||||
node.children.into_iter()
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DrawData<'a> {
|
||||
indent: String,
|
||||
percent_bar: String,
|
||||
display_data: &'a DisplayData,
|
||||
}
|
||||
|
||||
impl DrawData<'_> {
|
||||
fn get_new_indent(&self, has_children: bool, was_i_last: bool) -> String {
|
||||
let chars = self.display_data.get_tree_chars(was_i_last, has_children);
|
||||
self.indent.to_string() + chars
|
||||
}
|
||||
|
||||
// TODO: can we test this?
|
||||
fn generate_bar(&self, node: &DisplayNode, level: usize) -> String {
|
||||
let chars_in_bar = self.percent_bar.chars().count();
|
||||
let num_bars = chars_in_bar as f32 * self.display_data.percent_size(node);
|
||||
let mut num_not_my_bar = (chars_in_bar as i32) - num_bars as i32;
|
||||
|
||||
let mut new_bar = "".to_string();
|
||||
let idx = 5 - min(4, max(1, level));
|
||||
|
||||
for c in self.percent_bar.chars() {
|
||||
num_not_my_bar -= 1;
|
||||
if num_not_my_bar <= 0 {
|
||||
new_bar.push(BLOCKS[0]);
|
||||
} else if c == BLOCKS[0] {
|
||||
new_bar.push(BLOCKS[idx]);
|
||||
} else {
|
||||
new_bar.push(c);
|
||||
}
|
||||
}
|
||||
new_bar
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn draw_it(
|
||||
permissions: bool,
|
||||
use_full_path: bool,
|
||||
is_reversed: bool,
|
||||
no_colors: bool,
|
||||
root_node: Node,
|
||||
no_percent_bars: bool,
|
||||
terminal_width: usize,
|
||||
by_filecount: bool,
|
||||
root_node: &DisplayNode,
|
||||
iso: bool,
|
||||
skip_total: bool,
|
||||
) {
|
||||
if !permissions {
|
||||
eprintln!("Did not have permissions for all directories");
|
||||
}
|
||||
let biggest = match skip_total {
|
||||
false => root_node,
|
||||
true => root_node
|
||||
.get_children_from_node(false)
|
||||
.next()
|
||||
.unwrap_or(root_node),
|
||||
};
|
||||
|
||||
let num_chars_needed_on_left_most = if by_filecount {
|
||||
let max_size = biggest.size;
|
||||
max_size.separate_with_commas().chars().count()
|
||||
} else {
|
||||
find_biggest_size_str(root_node, iso)
|
||||
};
|
||||
|
||||
assert!(
|
||||
terminal_width > num_chars_needed_on_left_most + 2,
|
||||
"Not enough terminal width"
|
||||
);
|
||||
|
||||
let allowed_width = terminal_width - num_chars_needed_on_left_most - 2;
|
||||
let num_indent_chars = 3;
|
||||
let longest_string_length =
|
||||
find_longest_dir_name(root_node, num_indent_chars, allowed_width, !use_full_path);
|
||||
|
||||
let max_bar_length = if no_percent_bars || longest_string_length + 7 >= allowed_width as usize {
|
||||
0
|
||||
} else {
|
||||
allowed_width as usize - longest_string_length - 7
|
||||
};
|
||||
|
||||
let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect();
|
||||
|
||||
let display_data = DisplayData {
|
||||
short_paths: !use_full_path,
|
||||
is_reversed,
|
||||
colors_on: !no_colors,
|
||||
by_filecount,
|
||||
num_chars_needed_on_left_most,
|
||||
base_size: biggest.size,
|
||||
longest_string_length,
|
||||
ls_colors: LsColors::from_env().unwrap_or_default(),
|
||||
iso,
|
||||
};
|
||||
let draw_data = DrawData {
|
||||
indent: "".to_string(),
|
||||
percent_bar: first_size_bar,
|
||||
display_data: &display_data,
|
||||
};
|
||||
|
||||
for c in display_data.get_children_from_node(root_node) {
|
||||
let first_tree_chars = display_data.get_first_chars();
|
||||
display_node(c, true, first_tree_chars, &display_data)
|
||||
if !skip_total {
|
||||
display_node(root_node, &draw_data, true, true);
|
||||
} else {
|
||||
for (count, c) in root_node
|
||||
.get_children_from_node(draw_data.display_data.is_reversed)
|
||||
.enumerate()
|
||||
{
|
||||
let is_biggest = display_data.is_biggest(count, root_node.num_siblings());
|
||||
let was_i_last = display_data.is_last(count, root_node.num_siblings());
|
||||
display_node(c, &draw_data, is_biggest, was_i_last);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn display_node(node: Node, is_biggest: bool, indent: &str, display_data: &DisplayData) {
|
||||
let mut num_siblings = node.children.len() as u64;
|
||||
let max_sibling = num_siblings;
|
||||
let new_indent = clean_indentation_string(indent);
|
||||
let name = node.name.clone();
|
||||
let size = node.size;
|
||||
fn find_biggest_size_str(node: &DisplayNode, iso: bool) -> usize {
|
||||
let mut mx = human_readable_number(node.size, iso).chars().count();
|
||||
for n in node.children.iter() {
|
||||
mx = max(mx, find_biggest_size_str(n, iso));
|
||||
}
|
||||
mx
|
||||
}
|
||||
|
||||
if !display_data.is_reversed {
|
||||
print_this_node(&*name, size, is_biggest, display_data, indent);
|
||||
fn find_longest_dir_name(
|
||||
node: &DisplayNode,
|
||||
indent: usize,
|
||||
terminal: usize,
|
||||
long_paths: bool,
|
||||
) -> usize {
|
||||
let printable_name = get_printable_name(&node.name, long_paths);
|
||||
let longest = min(
|
||||
UnicodeWidthStr::width(&*printable_name) + 1 + indent,
|
||||
terminal,
|
||||
);
|
||||
|
||||
// each none root tree drawing is 2 more chars, hence we increment indent by 2
|
||||
node.children
|
||||
.iter()
|
||||
.map(|c| find_longest_dir_name(c, indent + 2, terminal, long_paths))
|
||||
.fold(longest, max)
|
||||
}
|
||||
|
||||
fn display_node(node: &DisplayNode, draw_data: &DrawData, is_biggest: bool, is_last: bool) {
|
||||
// hacky way of working out how deep we are in the tree
|
||||
let indent = draw_data.get_new_indent(!node.children.is_empty(), is_last);
|
||||
let level = ((indent.chars().count() - 1) / 2) - 1;
|
||||
let bar_text = draw_data.generate_bar(node, level);
|
||||
|
||||
let to_print = format_string(node, &indent, &bar_text, is_biggest, draw_data.display_data);
|
||||
|
||||
if !draw_data.display_data.is_reversed {
|
||||
println!("{}", to_print)
|
||||
}
|
||||
|
||||
for c in display_data.get_children_from_node(node) {
|
||||
num_siblings -= 1;
|
||||
let chars = display_data.get_tree_chars(num_siblings, max_sibling, !c.children.is_empty());
|
||||
let is_biggest = display_data.is_biggest(num_siblings, max_sibling);
|
||||
let full_indent = new_indent.clone() + chars;
|
||||
display_node(c, is_biggest, &*full_indent, display_data)
|
||||
let dd = DrawData {
|
||||
indent: clean_indentation_string(&indent),
|
||||
percent_bar: bar_text,
|
||||
display_data: draw_data.display_data,
|
||||
};
|
||||
|
||||
let num_siblings = node.num_siblings();
|
||||
|
||||
for (count, c) in node
|
||||
.get_children_from_node(draw_data.display_data.is_reversed)
|
||||
.enumerate()
|
||||
{
|
||||
let is_biggest = dd.display_data.is_biggest(count, num_siblings);
|
||||
let was_i_last = dd.display_data.is_last(count, num_siblings);
|
||||
display_node(c, &dd, is_biggest, was_i_last);
|
||||
}
|
||||
|
||||
if display_data.is_reversed {
|
||||
print_this_node(&*name, size, is_biggest, display_data, indent);
|
||||
if draw_data.display_data.is_reversed {
|
||||
println!("{}", to_print)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,52 +258,129 @@ fn clean_indentation_string(s: &str) -> String {
|
||||
is
|
||||
}
|
||||
|
||||
fn print_this_node(
|
||||
name: &str,
|
||||
size: u64,
|
||||
is_biggest: bool,
|
||||
display_data: &DisplayData,
|
||||
indentation: &str,
|
||||
) {
|
||||
let pretty_size = format!("{:>5}", human_readable_number(size),);
|
||||
println!(
|
||||
"{}",
|
||||
format_string(name, is_biggest, display_data, &*pretty_size, indentation)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn format_string(
|
||||
dir_name: &str,
|
||||
is_biggest: bool,
|
||||
display_data: &DisplayData,
|
||||
size: &str,
|
||||
indentation: &str,
|
||||
) -> String {
|
||||
fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool) -> String {
|
||||
let dir_name = dir_name.as_ref();
|
||||
let printable_name = {
|
||||
if display_data.short_paths {
|
||||
dir_name
|
||||
.split(std::path::is_separator)
|
||||
.last()
|
||||
.unwrap_or(dir_name)
|
||||
if long_paths {
|
||||
match dir_name.parent() {
|
||||
Some(prefix) => match dir_name.strip_prefix(prefix) {
|
||||
Ok(base) => base,
|
||||
Err(_) => dir_name,
|
||||
},
|
||||
None => dir_name,
|
||||
}
|
||||
} else {
|
||||
dir_name
|
||||
}
|
||||
};
|
||||
format!(
|
||||
"{} {} {}",
|
||||
if is_biggest && display_data.colors_on {
|
||||
Fixed(196).paint(size)
|
||||
} else {
|
||||
Style::new().paint(size)
|
||||
},
|
||||
indentation,
|
||||
printable_name,
|
||||
)
|
||||
encode_u8(printable_name.display().to_string().as_bytes())
|
||||
}
|
||||
|
||||
fn human_readable_number(size: u64) -> String {
|
||||
fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &DisplayData) -> String {
|
||||
let name = get_printable_name(&node.name, display_data.short_paths);
|
||||
let indent_and_name = format!("{} {}", indent, name);
|
||||
let width = UnicodeWidthStr::width(&*indent_and_name);
|
||||
|
||||
assert!(
|
||||
display_data.longest_string_length >= width,
|
||||
"Terminal width not wide enough to draw directory tree"
|
||||
);
|
||||
|
||||
// Add spaces after the filename so we can draw the % used bar chart.
|
||||
let name_and_padding = name
|
||||
+ " "
|
||||
.repeat(display_data.longest_string_length - width)
|
||||
.as_str();
|
||||
|
||||
name_and_padding
|
||||
}
|
||||
|
||||
fn maybe_trim_filename(name_in: String, indent: &str, display_data: &DisplayData) -> String {
|
||||
let indent_length = UnicodeWidthStr::width(indent);
|
||||
assert!(
|
||||
display_data.longest_string_length >= indent_length + 2,
|
||||
"Terminal width not wide enough to draw directory tree"
|
||||
);
|
||||
|
||||
let max_size = display_data.longest_string_length - indent_length;
|
||||
if UnicodeWidthStr::width(&*name_in) > max_size {
|
||||
let name = name_in.chars().take(max_size - 2).collect::<String>();
|
||||
name + ".."
|
||||
} else {
|
||||
name_in
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_string(
|
||||
node: &DisplayNode,
|
||||
indent: &str,
|
||||
percent_bar: &str,
|
||||
is_biggest: bool,
|
||||
display_data: &DisplayData,
|
||||
) -> String {
|
||||
let (percents, name_and_padding) = get_name_percent(node, indent, percent_bar, display_data);
|
||||
let pretty_size = get_pretty_size(node, is_biggest, display_data);
|
||||
let pretty_name = get_pretty_name(node, name_and_padding, display_data);
|
||||
format!("{} {} {}{}", pretty_size, indent, pretty_name, percents)
|
||||
}
|
||||
|
||||
fn get_name_percent(
|
||||
node: &DisplayNode,
|
||||
indent: &str,
|
||||
bar_chart: &str,
|
||||
display_data: &DisplayData,
|
||||
) -> (String, String) {
|
||||
if !bar_chart.is_empty() {
|
||||
let percent_size_str = format!("{:.0}%", display_data.percent_size(node) * 100.0);
|
||||
let percents = format!("│{} │ {:>4}", bar_chart, percent_size_str);
|
||||
let name_and_padding = pad_or_trim_filename(node, indent, display_data);
|
||||
(percents, name_and_padding)
|
||||
} else {
|
||||
let n = get_printable_name(&node.name, display_data.short_paths);
|
||||
let name = maybe_trim_filename(n, indent, display_data);
|
||||
("".into(), name)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayData) -> String {
|
||||
let output = if display_data.by_filecount {
|
||||
node.size.separate_with_commas()
|
||||
} else {
|
||||
human_readable_number(node.size, display_data.iso)
|
||||
};
|
||||
let spaces_to_add = display_data.num_chars_needed_on_left_most - output.chars().count();
|
||||
let output = " ".repeat(spaces_to_add) + output.as_str();
|
||||
|
||||
if is_biggest && display_data.colors_on {
|
||||
format!("{}", Red.paint(output))
|
||||
} else {
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pretty_name(
|
||||
node: &DisplayNode,
|
||||
name_and_padding: String,
|
||||
display_data: &DisplayData,
|
||||
) -> String {
|
||||
if display_data.colors_on {
|
||||
let meta_result = fs::metadata(&node.name);
|
||||
let directory_color = display_data
|
||||
.ls_colors
|
||||
.style_for_path_with_metadata(&node.name, meta_result.as_ref().ok());
|
||||
let ansi_style = directory_color
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or_default();
|
||||
format!("{}", ansi_style.paint(name_and_padding))
|
||||
} else {
|
||||
name_and_padding
|
||||
}
|
||||
}
|
||||
|
||||
fn human_readable_number(size: u64, iso: bool) -> String {
|
||||
for (i, u) in UNITS.iter().enumerate() {
|
||||
let marker = 1024u64.pow((UNITS.len() - i) as u32);
|
||||
let num: u64 = if iso { 1000 } else { 1024 };
|
||||
let marker = num.pow((UNITS.len() - i) as u32);
|
||||
if size >= marker {
|
||||
if size / marker < 10 {
|
||||
return format!("{:.1}{}", (size as f32 / marker as f32), u);
|
||||
@@ -190,24 +389,88 @@ fn human_readable_number(size: u64) -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
return format!("{}B", size);
|
||||
format!("{}B", size)
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
#[allow(unused_imports)]
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(test)]
|
||||
fn get_fake_display_data(longest_string_length: usize) -> DisplayData {
|
||||
DisplayData {
|
||||
short_paths: true,
|
||||
is_reversed: false,
|
||||
colors_on: false,
|
||||
by_filecount: false,
|
||||
num_chars_needed_on_left_most: 5,
|
||||
base_size: 1,
|
||||
longest_string_length,
|
||||
ls_colors: LsColors::from_env().unwrap_or_default(),
|
||||
iso: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_str() {
|
||||
let n = DisplayNode {
|
||||
name: PathBuf::from("/short"),
|
||||
size: 2_u64.pow(12), // This is 4.0K
|
||||
children: vec![],
|
||||
};
|
||||
let indent = "┌─┴";
|
||||
let percent_bar = "";
|
||||
let is_biggest = false;
|
||||
|
||||
let s = format_string(
|
||||
&n,
|
||||
indent,
|
||||
percent_bar,
|
||||
is_biggest,
|
||||
&get_fake_display_data(20),
|
||||
);
|
||||
assert_eq!(s, " 4.0K ┌─┴ short");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_str_long_name() {
|
||||
let name = "very_long_name_longer_than_the_eighty_character_limit_very_long_name_this_bit_will_truncate";
|
||||
let n = DisplayNode {
|
||||
name: PathBuf::from(name),
|
||||
size: 2_u64.pow(12), // This is 4.0K
|
||||
children: vec![],
|
||||
};
|
||||
let indent = "┌─┴";
|
||||
let percent_bar = "";
|
||||
let is_biggest = false;
|
||||
|
||||
let dd = get_fake_display_data(64);
|
||||
let s = format_string(&n, indent, percent_bar, is_biggest, &dd);
|
||||
assert_eq!(
|
||||
s,
|
||||
" 4.0K ┌─┴ very_long_name_longer_than_the_eighty_character_limit_very_.."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_human_readable_number() {
|
||||
assert_eq!(human_readable_number(1), "1B");
|
||||
assert_eq!(human_readable_number(956), "956B");
|
||||
assert_eq!(human_readable_number(1004), "1004B");
|
||||
assert_eq!(human_readable_number(1024), "1.0K");
|
||||
assert_eq!(human_readable_number(1536), "1.5K");
|
||||
assert_eq!(human_readable_number(1024 * 512), "512K");
|
||||
assert_eq!(human_readable_number(1024 * 1024), "1.0M");
|
||||
assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M");
|
||||
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G");
|
||||
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T");
|
||||
assert_eq!(human_readable_number(1, false), "1B");
|
||||
assert_eq!(human_readable_number(956, false), "956B");
|
||||
assert_eq!(human_readable_number(1004, false), "1004B");
|
||||
assert_eq!(human_readable_number(1024, false), "1.0K");
|
||||
assert_eq!(human_readable_number(1536, false), "1.5K");
|
||||
assert_eq!(human_readable_number(1024 * 512, false), "512K");
|
||||
assert_eq!(human_readable_number(1024 * 1024, false), "1.0M");
|
||||
assert_eq!(
|
||||
human_readable_number(1024 * 1024 * 1024 - 1, false),
|
||||
"1023M"
|
||||
);
|
||||
assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20, false), "20G");
|
||||
assert_eq!(
|
||||
human_readable_number(1024 * 1024 * 1024 * 1024, false),
|
||||
"1.0T"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
25
src/display_node.rs
Normal file
25
src/display_node.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct DisplayNode {
|
||||
// Note: the order of fields in important here, for PartialEq and PartialOrd
|
||||
pub size: u64,
|
||||
pub name: PathBuf, //todo: consider moving to a string?
|
||||
pub children: Vec<DisplayNode>,
|
||||
}
|
||||
|
||||
impl DisplayNode {
|
||||
pub fn num_siblings(&self) -> u64 {
|
||||
self.children.len() as u64
|
||||
}
|
||||
|
||||
pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = &DisplayNode> {
|
||||
// we box to avoid the clippy lint warning
|
||||
let out: Box<dyn Iterator<Item = &DisplayNode>> = if is_reversed {
|
||||
Box::new(self.children.iter().rev())
|
||||
} else {
|
||||
Box::new(self.children.iter())
|
||||
};
|
||||
out
|
||||
}
|
||||
}
|
||||
87
src/filter.rs
Normal file
87
src/filter.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use crate::display_node::DisplayNode;
|
||||
use crate::node::Node;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn get_biggest(
|
||||
top_level_nodes: Vec<Node>,
|
||||
min_size: Option<usize>,
|
||||
n: usize,
|
||||
depth: usize,
|
||||
using_a_filter: bool,
|
||||
) -> Option<DisplayNode> {
|
||||
if top_level_nodes.is_empty() {
|
||||
// perhaps change this, bring back Error object?
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut heap = BinaryHeap::new();
|
||||
let number_top_level_nodes = top_level_nodes.len();
|
||||
let root = get_new_root(top_level_nodes);
|
||||
let mut allowed_nodes = HashSet::new();
|
||||
|
||||
allowed_nodes.insert(root.name.as_path());
|
||||
heap = add_children(using_a_filter, min_size, &root, depth, heap);
|
||||
|
||||
for _ in number_top_level_nodes..n {
|
||||
let line = heap.pop();
|
||||
match line {
|
||||
Some(line) => {
|
||||
allowed_nodes.insert(line.name.as_path());
|
||||
heap = add_children(using_a_filter, min_size, line, depth, heap);
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
recursive_rebuilder(&allowed_nodes, &root)
|
||||
}
|
||||
|
||||
fn add_children<'a>(
|
||||
using_a_filter: bool,
|
||||
min_size: Option<usize>,
|
||||
file_or_folder: &'a Node,
|
||||
depth: usize,
|
||||
mut heap: BinaryHeap<&'a Node>,
|
||||
) -> BinaryHeap<&'a Node> {
|
||||
if depth > file_or_folder.depth {
|
||||
heap.extend(file_or_folder.children.iter().filter(|c| match min_size {
|
||||
Some(ms) => c.size > ms as u64,
|
||||
None => !using_a_filter || c.name.is_file() || c.size > 0,
|
||||
}))
|
||||
}
|
||||
heap
|
||||
}
|
||||
|
||||
fn get_new_root(top_level_nodes: Vec<Node>) -> Node {
|
||||
if top_level_nodes.len() != 1 {
|
||||
let size = top_level_nodes.iter().map(|node| node.size).sum();
|
||||
Node {
|
||||
name: PathBuf::from("(total)"),
|
||||
size,
|
||||
children: top_level_nodes,
|
||||
inode_device: None,
|
||||
depth: 0,
|
||||
}
|
||||
} else {
|
||||
top_level_nodes.into_iter().next().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn recursive_rebuilder(allowed_nodes: &HashSet<&Path>, current: &Node) -> Option<DisplayNode> {
|
||||
let mut new_children: Vec<_> = current
|
||||
.children
|
||||
.iter()
|
||||
.filter(|c| allowed_nodes.contains(c.name.as_path()))
|
||||
.filter_map(|c| recursive_rebuilder(allowed_nodes, c))
|
||||
.collect();
|
||||
|
||||
new_children.sort_by(|lhs, rhs| lhs.cmp(rhs).reverse());
|
||||
|
||||
Some(DisplayNode {
|
||||
name: current.name.clone(),
|
||||
size: current.size,
|
||||
children: new_children,
|
||||
})
|
||||
}
|
||||
75
src/filter_type.rs
Normal file
75
src/filter_type.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::display_node::DisplayNode;
|
||||
use crate::node::Node;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct ExtensionNode<'a> {
|
||||
size: u64,
|
||||
extension: Option<&'a OsStr>,
|
||||
}
|
||||
|
||||
pub fn get_all_file_types(top_level_nodes: &[Node], n: usize) -> Option<DisplayNode> {
|
||||
let ext_nodes = {
|
||||
let mut extension_cumulative_sizes = HashMap::new();
|
||||
build_by_all_file_types(top_level_nodes, &mut extension_cumulative_sizes);
|
||||
|
||||
let mut extension_cumulative_sizes: Vec<ExtensionNode<'_>> = extension_cumulative_sizes
|
||||
.iter()
|
||||
.map(|(&extension, &size)| ExtensionNode { extension, size })
|
||||
.collect();
|
||||
|
||||
extension_cumulative_sizes.sort_by(|lhs, rhs| lhs.cmp(rhs).reverse());
|
||||
|
||||
extension_cumulative_sizes
|
||||
};
|
||||
|
||||
let mut ext_nodes_iter = ext_nodes.iter();
|
||||
|
||||
// First, collect the first N - 1 nodes...
|
||||
let mut displayed: Vec<DisplayNode> = ext_nodes_iter
|
||||
.by_ref()
|
||||
.take(if n > 1 { n - 1 } else { 1 })
|
||||
.map(|node| DisplayNode {
|
||||
name: PathBuf::from(
|
||||
node.extension
|
||||
.map(|ext| format!(".{}", ext.to_string_lossy()))
|
||||
.unwrap_or_else(|| "(no extension)".to_owned()),
|
||||
),
|
||||
size: node.size,
|
||||
children: vec![],
|
||||
})
|
||||
.collect();
|
||||
|
||||
// ...then, aggregate the remaining nodes (if any) into a single "(others)" node
|
||||
if ext_nodes_iter.len() > 0 {
|
||||
displayed.push(DisplayNode {
|
||||
name: PathBuf::from("(others)"),
|
||||
size: ext_nodes_iter.map(|node| node.size).sum(),
|
||||
children: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let result = DisplayNode {
|
||||
name: PathBuf::from("(total)"),
|
||||
size: displayed.iter().map(|node| node.size).sum(),
|
||||
children: displayed,
|
||||
};
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn build_by_all_file_types<'a>(
|
||||
top_level_nodes: &'a [Node],
|
||||
counter: &mut HashMap<Option<&'a OsStr>, u64>,
|
||||
) {
|
||||
for node in top_level_nodes {
|
||||
if node.name.is_file() {
|
||||
let ext = node.name.extension();
|
||||
let cumulative_size = counter.entry(ext).or_default();
|
||||
*cumulative_size += node.size;
|
||||
}
|
||||
build_by_all_file_types(&node.children, counter)
|
||||
}
|
||||
}
|
||||
346
src/main.rs
346
src/main.rs
@@ -1,201 +1,193 @@
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
use self::display::draw_it;
|
||||
use crate::utils::is_a_parent_of;
|
||||
use clap::{App, AppSettings, Arg};
|
||||
use utils::{find_big_ones, get_dir_tree, simplify_dir_names, sort, trim_deep_ones, Node};
|
||||
|
||||
mod cli;
|
||||
mod config;
|
||||
mod dir_walker;
|
||||
mod display;
|
||||
mod display_node;
|
||||
mod filter;
|
||||
mod filter_type;
|
||||
mod node;
|
||||
mod platform;
|
||||
mod utils;
|
||||
|
||||
static DEFAULT_NUMBER_OF_LINES: usize = 20;
|
||||
use crate::cli::build_cli;
|
||||
use std::collections::HashSet;
|
||||
use std::process;
|
||||
|
||||
#[cfg(windows)]
|
||||
fn init_color() {
|
||||
ansi_term::enable_ansi_support().expect("Couldn't enable color support");
|
||||
use self::display::draw_it;
|
||||
use clap::Values;
|
||||
use config::get_config;
|
||||
use dir_walker::{walk_it, WalkData};
|
||||
use filter::get_biggest;
|
||||
use filter_type::get_all_file_types;
|
||||
use regex::Regex;
|
||||
use std::cmp::max;
|
||||
use std::path::PathBuf;
|
||||
use terminal_size::{terminal_size, Height, Width};
|
||||
use utils::get_filesystem_devices;
|
||||
use utils::simplify_dir_names;
|
||||
|
||||
static DEFAULT_NUMBER_OF_LINES: usize = 30;
|
||||
static DEFAULT_TERMINAL_WIDTH: usize = 80;
|
||||
|
||||
fn init_color(no_color: bool) -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// If no color is already set do not print a warning message
|
||||
if no_color {
|
||||
true
|
||||
} else {
|
||||
// Required for windows 10
|
||||
// Fails to resolve for windows 8 so disable color
|
||||
match ansi_term::enable_ansi_support() {
|
||||
Ok(_) => no_color,
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"This version of Windows does not support ANSI colors, setting no_color flag"
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
no_color
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn init_color() {}
|
||||
fn get_height_of_terminal() -> usize {
|
||||
// Simplify once https://github.com/eminence/terminal-size/pull/41 is
|
||||
// merged
|
||||
terminal_size()
|
||||
// Windows CI runners detect a terminal height of 0
|
||||
.map(|(_, Height(h))| max(h as usize, DEFAULT_NUMBER_OF_LINES))
|
||||
.unwrap_or(DEFAULT_NUMBER_OF_LINES)
|
||||
- 10
|
||||
}
|
||||
|
||||
fn get_width_of_terminal() -> usize {
|
||||
// Simplify once https://github.com/eminence/terminal-size/pull/41 is
|
||||
// merged
|
||||
terminal_size()
|
||||
.map(|(Width(w), _)| match cfg!(windows) {
|
||||
// Windows CI runners detect a very low terminal width
|
||||
true => max(w as usize, DEFAULT_TERMINAL_WIDTH),
|
||||
false => w as usize,
|
||||
})
|
||||
.unwrap_or(DEFAULT_TERMINAL_WIDTH)
|
||||
}
|
||||
|
||||
fn get_regex_value(maybe_value: Option<Values>) -> Vec<Regex> {
|
||||
maybe_value
|
||||
.unwrap_or_default()
|
||||
.map(|reg| {
|
||||
Regex::new(reg).unwrap_or_else(|err| {
|
||||
eprintln!("Ignoring bad value for regex {:?}", err);
|
||||
process::exit(1)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
init_color();
|
||||
let def_num_str = DEFAULT_NUMBER_OF_LINES.to_string();
|
||||
let options = App::new("Dust")
|
||||
.about("Like du but more intuitive")
|
||||
.version(crate_version!())
|
||||
.setting(AppSettings::TrailingVarArg)
|
||||
.arg(
|
||||
Arg::with_name("depth")
|
||||
.short("d")
|
||||
.long("depth")
|
||||
.help("Depth to show")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.short("t")
|
||||
.long("threads")
|
||||
.help("Number of threads to spawn simultaneously")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("number_of_lines")
|
||||
.short("n")
|
||||
.long("number-of-lines")
|
||||
.help("Number of lines of output to show")
|
||||
.takes_value(true)
|
||||
.default_value(def_num_str.as_ref()),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("display_full_paths")
|
||||
.short("p")
|
||||
.long("full-paths")
|
||||
.help("If set sub directories will not have their path shortened"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore_directory")
|
||||
.short("X")
|
||||
.long("ignore-directory")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.multiple(true)
|
||||
.help("Exclude any file or directory with contains this substring."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("limit_filesystem")
|
||||
.short("x")
|
||||
.long("limit-filesystem")
|
||||
.help("Only count the files and directories in the same filesystem as the supplied directory"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("display_apparent_size")
|
||||
.short("s")
|
||||
.long("apparent-size")
|
||||
.help("If set will use file length. Otherwise we use blocks"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("reverse")
|
||||
.short("r")
|
||||
.long("reverse")
|
||||
.help("If applied tree will be printed upside down (biggest lowest)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no_colors")
|
||||
.short("c")
|
||||
.long("no_colors")
|
||||
.help("If applied no colors will be printed (normally largest directories are marked in red"),
|
||||
)
|
||||
.arg(Arg::with_name("inputs").multiple(true))
|
||||
.get_matches();
|
||||
let options = build_cli().get_matches();
|
||||
let config = get_config();
|
||||
|
||||
let target_dirs = {
|
||||
match options.values_of("inputs") {
|
||||
None => vec!["."],
|
||||
Some(r) => r.collect(),
|
||||
}
|
||||
let target_dirs = options
|
||||
.values_of("inputs")
|
||||
.expect("Should be a default value here")
|
||||
.collect();
|
||||
|
||||
let summarize_file_types = options.is_present("types");
|
||||
|
||||
let filter_regexs = get_regex_value(options.values_of("filter"));
|
||||
let invert_filter_regexs = get_regex_value(options.values_of("invert_filter"));
|
||||
|
||||
let terminal_width = options
|
||||
.value_of_t("width")
|
||||
.unwrap_or_else(|_| get_width_of_terminal());
|
||||
|
||||
let depth = options.value_of_t("depth").unwrap_or(usize::MAX);
|
||||
|
||||
// If depth is set, then we set the default number_of_lines to be max
|
||||
// instead of screen height
|
||||
let default_height = if depth != usize::MAX {
|
||||
usize::MAX
|
||||
} else {
|
||||
get_height_of_terminal()
|
||||
};
|
||||
|
||||
let number_of_lines = match value_t!(options.value_of("number_of_lines"), usize) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
eprintln!("Ignoring bad value for number_of_lines");
|
||||
DEFAULT_NUMBER_OF_LINES
|
||||
}
|
||||
};
|
||||
let number_of_lines = options
|
||||
.value_of("number_of_lines")
|
||||
.and_then(|v| {
|
||||
v.parse()
|
||||
.map_err(|_| eprintln!("Ignoring bad value for number_of_lines"))
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or(default_height);
|
||||
|
||||
let temp_threads = options.value_of("threads").and_then(|threads| {
|
||||
threads
|
||||
.parse::<usize>()
|
||||
.map_err(|_| eprintln!("Ignoring bad value for threads: {:?}", threads))
|
||||
.ok()
|
||||
});
|
||||
// Bug in JWalk
|
||||
// https://github.com/jessegrosjean/jwalk/issues/15
|
||||
// We force it to use 2 threads if there is only 1 cpu
|
||||
// as JWalk breaks if it tries to run on a single cpu
|
||||
let threads = {
|
||||
if temp_threads.is_none() && num_cpus::get() == 1 {
|
||||
Some(2)
|
||||
} else {
|
||||
temp_threads
|
||||
}
|
||||
};
|
||||
let no_colors = init_color(config.get_no_colors(&options));
|
||||
|
||||
let depth = options.value_of("depth").and_then(|depth| {
|
||||
depth
|
||||
.parse::<u64>()
|
||||
.map(|v| v + 1)
|
||||
.map_err(|_| eprintln!("Ignoring bad value for depth"))
|
||||
.ok()
|
||||
});
|
||||
if options.is_present("depth") && number_of_lines != DEFAULT_NUMBER_OF_LINES {
|
||||
eprintln!("Use either -n or -d. Not both");
|
||||
return;
|
||||
}
|
||||
let ignore_directories = options
|
||||
.values_of("ignore_directory")
|
||||
.unwrap_or_default()
|
||||
.map(PathBuf::from);
|
||||
|
||||
let use_apparent_size = options.is_present("display_apparent_size");
|
||||
let by_filecount = options.is_present("by_filecount");
|
||||
let limit_filesystem = options.is_present("limit_filesystem");
|
||||
let ignore_directories = options.values_of("ignore_directory").map(|r| r.collect());
|
||||
|
||||
let simplified_dirs = simplify_dir_names(target_dirs);
|
||||
let (permissions, nodes) = get_dir_tree(
|
||||
&simplified_dirs,
|
||||
ignore_directories,
|
||||
use_apparent_size,
|
||||
limit_filesystem,
|
||||
threads,
|
||||
);
|
||||
let sorted_data = sort(nodes);
|
||||
let biggest_ones = {
|
||||
match depth {
|
||||
None => find_big_ones(sorted_data, number_of_lines + simplified_dirs.len()),
|
||||
Some(d) => trim_deep_ones(sorted_data, d, &simplified_dirs),
|
||||
}
|
||||
let allowed_filesystems = limit_filesystem
|
||||
.then(|| get_filesystem_devices(simplified_dirs.iter()))
|
||||
.unwrap_or_default();
|
||||
|
||||
let ignored_full_path: HashSet<PathBuf> = ignore_directories
|
||||
.flat_map(|x| simplified_dirs.iter().map(move |d| d.join(&x)))
|
||||
.collect();
|
||||
|
||||
let walk_data = WalkData {
|
||||
ignore_directories: ignored_full_path,
|
||||
filter_regex: &filter_regexs,
|
||||
invert_filter_regex: &invert_filter_regexs,
|
||||
allowed_filesystems,
|
||||
use_apparent_size: config.get_apparent_size(&options),
|
||||
by_filecount,
|
||||
ignore_hidden: config.get_ignore_hidden(&options),
|
||||
};
|
||||
let tree = build_tree(biggest_ones, depth);
|
||||
// Larger stack size to handle cases with lots of nested directories
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.stack_size(usize::pow(1024, 3))
|
||||
.build_global()
|
||||
.unwrap_or_else(|e| eprintln!("Warning: Could not configure threads {:?}", e));
|
||||
|
||||
draw_it(
|
||||
permissions,
|
||||
options.is_present("display_full_paths"),
|
||||
options.is_present("reverse"),
|
||||
options.is_present("no_colors"),
|
||||
tree,
|
||||
);
|
||||
}
|
||||
let iso = config.get_iso(&options);
|
||||
let (top_level_nodes, has_errors) = walk_it(simplified_dirs, walk_data);
|
||||
|
||||
fn build_tree(biggest_ones: Vec<(String, u64)>, depth: Option<u64>) -> Node {
|
||||
let mut top_parent = Node::default();
|
||||
let tree = match summarize_file_types {
|
||||
true => get_all_file_types(&top_level_nodes, number_of_lines),
|
||||
false => get_biggest(
|
||||
top_level_nodes,
|
||||
config.get_min_size(&options, iso),
|
||||
number_of_lines,
|
||||
depth,
|
||||
options.values_of("filter").is_some() || options.value_of("invert_filter").is_some(),
|
||||
),
|
||||
};
|
||||
|
||||
// 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);
|
||||
if has_errors {
|
||||
eprintln!("Did not have permissions for all directories");
|
||||
}
|
||||
top_parent
|
||||
}
|
||||
|
||||
fn recursively_build_tree(parent_node: &mut Node, new_node: Node, depth: Option<u64>) {
|
||||
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);
|
||||
if let Some(root_node) = tree {
|
||||
draw_it(
|
||||
config.get_full_paths(&options),
|
||||
!config.get_reverse(&options),
|
||||
no_colors,
|
||||
config.get_no_bars(&options),
|
||||
terminal_width,
|
||||
by_filecount,
|
||||
&root_node,
|
||||
iso,
|
||||
config.get_skip_total(&options),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
78
src/node.rs
Normal file
78
src/node.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use crate::platform::get_metadata;
|
||||
use crate::utils::is_filtered_out_due_to_invert_regex;
|
||||
use crate::utils::is_filtered_out_due_to_regex;
|
||||
|
||||
use regex::Regex;
|
||||
use std::cmp::Ordering;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Eq, Clone)]
|
||||
pub struct Node {
|
||||
pub name: PathBuf,
|
||||
pub size: u64,
|
||||
pub children: Vec<Node>,
|
||||
pub inode_device: Option<(u64, u64)>,
|
||||
pub depth: usize,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_node(
|
||||
dir: PathBuf,
|
||||
children: Vec<Node>,
|
||||
filter_regex: &[Regex],
|
||||
invert_filter_regex: &[Regex],
|
||||
use_apparent_size: bool,
|
||||
is_symlink: bool,
|
||||
is_file: bool,
|
||||
by_filecount: bool,
|
||||
depth: usize,
|
||||
) -> Option<Node> {
|
||||
get_metadata(&dir, use_apparent_size).map(|data| {
|
||||
let inode_device = if is_symlink && !use_apparent_size {
|
||||
None
|
||||
} else {
|
||||
data.1
|
||||
};
|
||||
|
||||
let size = if is_filtered_out_due_to_regex(filter_regex, &dir)
|
||||
|| is_filtered_out_due_to_invert_regex(invert_filter_regex, &dir)
|
||||
|| (is_symlink && !use_apparent_size)
|
||||
|| by_filecount && !is_file
|
||||
{
|
||||
0
|
||||
} else if by_filecount {
|
||||
1
|
||||
} else {
|
||||
data.0
|
||||
};
|
||||
|
||||
Node {
|
||||
name: dir,
|
||||
size,
|
||||
children,
|
||||
inode_device,
|
||||
depth,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name && self.size == other.size && self.children == other.children
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Node {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.size
|
||||
.cmp(&other.size)
|
||||
.then_with(|| self.name.cmp(&other.name))
|
||||
.then_with(|| self.children.cmp(&other.children))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Node {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
128
src/platform.rs
Normal file
128
src/platform.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
#[allow(unused_imports)]
|
||||
use std::fs;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
fn get_block_size() -> u64 {
|
||||
// All os specific implementations of MetadataExt seem to define a block as 512 bytes
|
||||
// https://doc.rust-lang.org/std/os/linux/fs/trait.MetadataExt.html#tymethod.st_blocks
|
||||
512
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
match d.metadata() {
|
||||
Ok(md) => {
|
||||
if use_apparent_size {
|
||||
Some((md.len(), Some((md.ino(), md.dev()))))
|
||||
} else {
|
||||
Some((md.blocks() * get_block_size(), Some((md.ino(), md.dev()))))
|
||||
}
|
||||
}
|
||||
Err(_e) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
||||
// On windows opening the file to get size, file ID and volume can be very
|
||||
// expensive because 1) it causes a few system calls, and more importantly 2) it can cause
|
||||
// windows defender to scan the file.
|
||||
// Therefore we try to avoid doing that for common cases, mainly those of
|
||||
// plain files:
|
||||
|
||||
// The idea is to make do with the file size that we get from the OS for
|
||||
// free as part of iterating a folder. Therefore we want to make sure that
|
||||
// it makes sense to use that free size information:
|
||||
|
||||
// Volume boundaries:
|
||||
// The user can ask us not to cross volume boundaries. If the DirEntry is a
|
||||
// plain file and not a reparse point or other non-trivial stuff, we assume
|
||||
// that the file is located on the same volume as the directory that
|
||||
// contains it.
|
||||
|
||||
// File ID:
|
||||
// This optimization does deprive us of access to a file ID. As a
|
||||
// workaround, we just make one up that hopefully does not collide with real
|
||||
// file IDs.
|
||||
// Hard links: Unresolved. We don't get inode/file index, so hard links
|
||||
// count once for each link. Hopefully they are not too commonly in use on
|
||||
// windows.
|
||||
|
||||
// Size:
|
||||
// We assume (naively?) that for the common cases the free size info is the
|
||||
// same as one would get by doing the expensive thing. Sparse, encrypted and
|
||||
// compressed files are not included in the common cases, as one can image
|
||||
// there being more than view on their size.
|
||||
|
||||
// Savings in orders of magnitude in terms of time, io and cpu have been
|
||||
// observed on hdd, windows 10, some 100Ks files taking up some hundreds of
|
||||
// GBs:
|
||||
// Consistently opening the file: 30 minutes.
|
||||
// With this optimization: 8 sec.
|
||||
|
||||
use std::io;
|
||||
use winapi_util::Handle;
|
||||
fn handle_from_path_limited<P: AsRef<Path>>(path: P) -> io::Result<Handle> {
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
const FILE_READ_ATTRIBUTES: u32 = 0x0080;
|
||||
|
||||
// So, it seems that it does does have to be that expensive to open
|
||||
// files to get their info: Avoiding opening the file with the full
|
||||
// GENERIC_READ is key:
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights:
|
||||
// "For example, a Windows file object maps the GENERIC_READ bit to the
|
||||
// READ_CONTROL and SYNCHRONIZE standard access rights and to the
|
||||
// FILE_READ_DATA, FILE_READ_EA, and FILE_READ_ATTRIBUTES
|
||||
// object-specific access rights"
|
||||
|
||||
// The flag FILE_READ_DATA seems to be the expensive one, so we'll avoid
|
||||
// that, and a most of the other ones. Simply because it seems that we
|
||||
// don't need them.
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.access_mode(FILE_READ_ATTRIBUTES)
|
||||
.open(path)?;
|
||||
Ok(Handle::from_file(file))
|
||||
}
|
||||
|
||||
fn get_metadata_expensive(d: &Path) -> Option<(u64, Option<(u64, u64)>)> {
|
||||
use winapi_util::file::information;
|
||||
|
||||
let h = handle_from_path_limited(d).ok()?;
|
||||
let info = information(&h).ok()?;
|
||||
|
||||
Some((
|
||||
info.file_size(),
|
||||
Some((info.file_index(), info.volume_serial_number())),
|
||||
))
|
||||
}
|
||||
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
match d.metadata() {
|
||||
Ok(ref md) => {
|
||||
const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20;
|
||||
const FILE_ATTRIBUTE_READONLY: u32 = 0x01;
|
||||
const FILE_ATTRIBUTE_HIDDEN: u32 = 0x02;
|
||||
const FILE_ATTRIBUTE_SYSTEM: u32 = 0x04;
|
||||
const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
|
||||
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
|
||||
|
||||
let attr_filtered = md.file_attributes()
|
||||
& !(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
|
||||
if (attr_filtered & FILE_ATTRIBUTE_ARCHIVE) != 0
|
||||
|| (attr_filtered & FILE_ATTRIBUTE_DIRECTORY) != 0
|
||||
|| md.file_attributes() == FILE_ATTRIBUTE_NORMAL
|
||||
{
|
||||
Some((md.len(), None))
|
||||
} else {
|
||||
get_metadata_expensive(d)
|
||||
}
|
||||
}
|
||||
_ => get_metadata_expensive(d),
|
||||
}
|
||||
}
|
||||
412
src/tests.rs
412
src/tests.rs
@@ -1,412 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
use crate::display::DisplayData;
|
||||
use display::format_string;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::panic;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tempfile::Builder;
|
||||
use tempfile::TempDir;
|
||||
|
||||
// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_main() {
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["src/test_dir"])
|
||||
.stdout()
|
||||
.is(main_output(true).as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_main_long_paths() {
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-p", "src/test_dir"])
|
||||
.stdout()
|
||||
.is(main_output(false).as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_main_multi_arg() {
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["src/test_dir/many/", "src/test_dir/", "src/test_dir"])
|
||||
.stdout()
|
||||
.is(main_output(true).as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn main_output(short_paths: bool) -> String {
|
||||
let d = DisplayData {
|
||||
short_paths,
|
||||
is_reversed: false,
|
||||
colors_on: true,
|
||||
};
|
||||
format!(
|
||||
"{}
|
||||
{}
|
||||
{}
|
||||
{}",
|
||||
format_string("src/test_dir", true, &d, " 4.0K", "─┬"),
|
||||
format_string("src/test_dir/many", true, &d, " 4.0K", " └─┬",),
|
||||
format_string("src/test_dir/many/hello_file", true, &d, " 4.0K", " ├──",),
|
||||
format_string("src/test_dir/many/a_file", false, &d, " 0B", " └──",),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn main_output(short_paths: bool) -> String {
|
||||
let d = DisplayData {
|
||||
short_paths,
|
||||
is_reversed: false,
|
||||
colors_on: true,
|
||||
};
|
||||
format!(
|
||||
"{}
|
||||
{}
|
||||
{}
|
||||
{}",
|
||||
format_string("src/test_dir", true, &d, " 12K", "─┬"),
|
||||
format_string("src/test_dir/many", true, &d, " 8.0K", " └─┬",),
|
||||
format_string("src/test_dir/many/hello_file", true, &d, " 4.0K", " ├──",),
|
||||
format_string("src/test_dir/many/a_file", false, &d, " 0B", " └──",),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn main_output(short_paths: bool) -> String {
|
||||
let d = DisplayData {
|
||||
short_paths,
|
||||
is_reversed: false,
|
||||
colors_on: true,
|
||||
};
|
||||
format!(
|
||||
"{}
|
||||
{}
|
||||
{}
|
||||
{}",
|
||||
format_string("src/test_dir", true, &d, " 6B", "─┬"),
|
||||
format_string("src/test_dir\\many", true, &d, " 6B", " └─┬",),
|
||||
format_string(
|
||||
"src/test_dir\\many\\hello_file",
|
||||
true,
|
||||
&d,
|
||||
" 6B",
|
||||
" ├──",
|
||||
),
|
||||
format_string("src/test_dir\\many\\a_file", false, &d, " 0B", " └──",),
|
||||
)
|
||||
}
|
||||
|
||||
// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_no_color_flag() {
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-c", "src/test_dir/"])
|
||||
.stdout()
|
||||
.is(no_color_flag_output().as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn no_color_flag_output() -> String {
|
||||
"
|
||||
4.0K ─┬ test_dir
|
||||
4.0K └─┬ many
|
||||
4.0K ├── hello_file
|
||||
0B └── a_file
|
||||
"
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn no_color_flag_output() -> String {
|
||||
"
|
||||
12K ─┬ test_dir
|
||||
8.0K └─┬ many
|
||||
4.0K ├── hello_file
|
||||
0B └── a_file
|
||||
"
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn no_color_flag_output() -> String {
|
||||
"
|
||||
6B ─┬ test_dir
|
||||
6B └─┬ many
|
||||
6B ├── hello_file
|
||||
0B └── a_file
|
||||
"
|
||||
.to_string()
|
||||
}
|
||||
|
||||
// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_apparent_size() {
|
||||
let d = DisplayData {
|
||||
short_paths: true,
|
||||
is_reversed: false,
|
||||
colors_on: true,
|
||||
};
|
||||
let r = format!(
|
||||
"{}",
|
||||
format_string("src/test_dir/many/hello_file", true, &d, " 6B", " ├──",),
|
||||
);
|
||||
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-s", "src/test_dir"])
|
||||
.stdout()
|
||||
.contains(r.as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_reverse_flag() {
|
||||
// variable names the same length make the output easier to read
|
||||
let a = " ┌── a_file";
|
||||
let b = " ├── hello_file";
|
||||
let c = " ┌─┴ many";
|
||||
let d = " ─┴ test_dir";
|
||||
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-r", "src/test_dir"])
|
||||
.stdout()
|
||||
.contains(a)
|
||||
.stdout()
|
||||
.contains(b)
|
||||
.stdout()
|
||||
.contains(c)
|
||||
.stdout()
|
||||
.contains(d)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_d_flag_works() {
|
||||
// We should see the top level directory but not the sub dirs / files:
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-d", "1", "-s", "src/test_dir"])
|
||||
.stdout()
|
||||
.doesnt_contain("hello_file")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn build_temp_file(dir: &TempDir) -> PathBuf {
|
||||
let file_path = dir.path().join("notes.txt");
|
||||
let mut file = File::create(&file_path).unwrap();
|
||||
writeln!(file, "I am a temp file").unwrap();
|
||||
file_path
|
||||
}
|
||||
|
||||
// fix! [rivy; 2020-01-22] possible on "windows"?; `ln` is not usually an available command; creation of symbolic links requires special enhanced permissions
|
||||
// ... ref: <https://superuser.com/questions/343074/directory-junction-vs-directory-symbolic-link> @@ <https://archive.is/gpTLE>
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_soft_sym_link() {
|
||||
let dir = Builder::new().tempdir().unwrap();
|
||||
let file = build_temp_file(&dir);
|
||||
let dir_s = dir.path().to_str().unwrap();
|
||||
let file_path_s = file.to_str().unwrap();
|
||||
|
||||
let link_name = dir.path().join("the_link");
|
||||
let link_name_s = link_name.to_str().unwrap();
|
||||
let c = Command::new("ln")
|
||||
.arg("-s")
|
||||
.arg(file_path_s)
|
||||
.arg(link_name_s)
|
||||
.output();
|
||||
assert!(c.is_ok());
|
||||
|
||||
let a = format!(" ─┬ {}", dir_s);
|
||||
let b = format!(" ├── {}", file_path_s);
|
||||
let c = format!(" └── {}", link_name_s);
|
||||
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-p", &dir_s])
|
||||
.stdout()
|
||||
.contains(a.as_str())
|
||||
.stdout()
|
||||
.contains(b.as_str())
|
||||
.stdout()
|
||||
.contains(c.as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Hard links are ignored as the inode is the same as the file
|
||||
// fix! [rivy; 2020-01-22] may fail on "usual" windows hosts as `ln` is not usually an available command
|
||||
#[test]
|
||||
pub fn test_hard_sym_link() {
|
||||
let dir = Builder::new().tempdir().unwrap();
|
||||
let file = build_temp_file(&dir);
|
||||
let dir_s = dir.path().to_str().unwrap();
|
||||
let file_path_s = file.to_str().unwrap();
|
||||
|
||||
let link_name = dir.path().join("the_link");
|
||||
let link_name_s = link_name.to_str().unwrap();
|
||||
let c = Command::new("ln")
|
||||
.arg(file_path_s)
|
||||
.arg(link_name_s)
|
||||
.output();
|
||||
assert!(c.is_ok());
|
||||
|
||||
let a = format!(" ─┬ {}", dir_s);
|
||||
let b = format!(" └── {}", link_name_s);
|
||||
let b2 = format!(" └── {}", file_path_s);
|
||||
|
||||
// Because this is a hard link the file and hard link look identical. Therefore
|
||||
// we cannot guarantee which version will appear first.
|
||||
let result = panic::catch_unwind(|| {
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-p", dir_s])
|
||||
.stdout()
|
||||
.contains(a.as_str())
|
||||
.stdout()
|
||||
.contains(b.as_str())
|
||||
.unwrap();
|
||||
});
|
||||
if result.is_err() {
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-p", dir_s])
|
||||
.stdout()
|
||||
.contains(a.as_str())
|
||||
.stdout()
|
||||
.contains(b2.as_str())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Check we don't recurse down an infinite symlink tree
|
||||
// fix! [rivy; 2020-01-22] possible on "windows"?; `ln` is not usually an available command; creation of symbolic links requires special enhanced permissions
|
||||
// ... ref: <https://superuser.com/questions/343074/directory-junction-vs-directory-symbolic-link> @@ <https://archive.is/gpTLE>
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_recursive_sym_link() {
|
||||
let dir = Builder::new().tempdir().unwrap();
|
||||
let dir_s = dir.path().to_str().unwrap();
|
||||
|
||||
let link_name = dir.path().join("the_link");
|
||||
let link_name_s = link_name.to_str().unwrap();
|
||||
|
||||
let c = Command::new("ln")
|
||||
.arg("-s")
|
||||
.arg(dir_s)
|
||||
.arg(link_name_s)
|
||||
.output();
|
||||
assert!(c.is_ok());
|
||||
|
||||
let a = format!(" ─┬ {}", dir_s);
|
||||
let b = format!(" └── {}", link_name_s);
|
||||
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-p", dir_s])
|
||||
.stdout()
|
||||
.contains(a.as_str())
|
||||
.stdout()
|
||||
.contains(b.as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Check against directories and files whos names are substrings of each other
|
||||
// fix! [rivy; 2020-22-01] "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_substring_of_names() {
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-c", "src/test_dir2"])
|
||||
.stdout()
|
||||
.is(no_substring_of_names_output().as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn no_substring_of_names_output() -> String {
|
||||
"
|
||||
24K ─┬ test_dir2
|
||||
8.0K ├─┬ dir
|
||||
4.0K │ └── hello
|
||||
8.0K ├─┬ dir_substring
|
||||
4.0K │ └── hello
|
||||
4.0K └── dir_name_clash
|
||||
"
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn no_substring_of_names_output() -> String {
|
||||
"
|
||||
12K ─┬ test_dir2
|
||||
4.0K ├─┬ dir
|
||||
4.0K │ └── hello
|
||||
4.0K ├── dir_name_clash
|
||||
4.0K └─┬ dir_substring
|
||||
4.0K └── hello
|
||||
"
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn no_substring_of_names_output() -> String {
|
||||
"
|
||||
16B ─┬ test_dir2
|
||||
6B ├─┬ dir_substring
|
||||
6B │ └── hello
|
||||
5B ├─┬ dir
|
||||
5B │ └── hello
|
||||
5B └── dir_name_clash
|
||||
"
|
||||
.into()
|
||||
}
|
||||
|
||||
// Check against directories and files whos names are substrings of each other
|
||||
#[test]
|
||||
pub fn test_ignore_dir() {
|
||||
assert_cli::Assert::main_binary()
|
||||
.with_args(&["-c", "-X", "dir_substring", "src/test_dir2"])
|
||||
.stdout()
|
||||
.is(ignore_dir_output().as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn ignore_dir_output() -> String {
|
||||
"
|
||||
16K ─┬ test_dir2
|
||||
8.0K ├─┬ dir
|
||||
4.0K │ └── hello
|
||||
4.0K └── dir_name_clash
|
||||
"
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn ignore_dir_output() -> String {
|
||||
"
|
||||
8.0K ─┬ test_dir2
|
||||
4.0K ├─┬ dir
|
||||
4.0K │ └── hello
|
||||
4.0K └── dir_name_clash
|
||||
"
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn ignore_dir_output() -> String {
|
||||
"
|
||||
10B ─┬ test_dir2
|
||||
5B ├─┬ dir
|
||||
5B │ └── hello
|
||||
5B └── dir_name_clash
|
||||
"
|
||||
.into()
|
||||
}
|
||||
158
src/utils.rs
Normal file
158
src/utils.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use platform::get_metadata;
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::platform;
|
||||
use regex::Regex;
|
||||
|
||||
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
|
||||
let mut top_level_names: HashSet<PathBuf> = HashSet::with_capacity(filenames.len());
|
||||
|
||||
for t in filenames {
|
||||
let top_level_name = normalize_path(t);
|
||||
let mut can_add = true;
|
||||
let mut to_remove: Vec<PathBuf> = Vec::new();
|
||||
|
||||
for tt in top_level_names.iter() {
|
||||
if is_a_parent_of(&top_level_name, tt) {
|
||||
to_remove.push(tt.to_path_buf());
|
||||
} else if is_a_parent_of(tt, &top_level_name) {
|
||||
can_add = false;
|
||||
}
|
||||
}
|
||||
for r in to_remove {
|
||||
top_level_names.remove(&r);
|
||||
}
|
||||
if can_add {
|
||||
top_level_names.insert(top_level_name);
|
||||
}
|
||||
}
|
||||
|
||||
top_level_names
|
||||
}
|
||||
|
||||
pub fn get_filesystem_devices<'a, P: IntoIterator<Item = &'a PathBuf>>(paths: P) -> HashSet<u64> {
|
||||
// Gets the device ids for the filesystems which are used by the argument paths
|
||||
paths
|
||||
.into_iter()
|
||||
.filter_map(|p| match get_metadata(p, false) {
|
||||
Some((_size, Some((_id, dev)))) => Some(dev),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||
// normalize path ...
|
||||
// 1. removing repeated separators
|
||||
// 2. removing interior '.' ("current directory") path segments
|
||||
// 3. removing trailing extra separators and '.' ("current directory") path segments
|
||||
// * `Path.components()` does all the above work; ref: <https://doc.rust-lang.org/std/path/struct.Path.html#method.components>
|
||||
// 4. changing to os preferred separator (automatically done by recollecting components back into a PathBuf)
|
||||
path.as_ref().components().collect()
|
||||
}
|
||||
|
||||
pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool {
|
||||
if filter_regex.is_empty() {
|
||||
false
|
||||
} else {
|
||||
filter_regex
|
||||
.iter()
|
||||
.all(|f| !f.is_match(&dir.as_os_str().to_string_lossy()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_filtered_out_due_to_invert_regex(filter_regex: &[Regex], dir: &Path) -> bool {
|
||||
filter_regex
|
||||
.iter()
|
||||
.any(|f| f.is_match(&dir.as_os_str().to_string_lossy()))
|
||||
}
|
||||
|
||||
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
|
||||
let parent = parent.as_ref();
|
||||
let child = child.as_ref();
|
||||
child.starts_with(parent) && !parent.starts_with(child)
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_simplify_dir() {
|
||||
let mut correct = HashSet::new();
|
||||
correct.insert(PathBuf::from("a"));
|
||||
assert_eq!(simplify_dir_names(vec!["a"]), correct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_dir_rm_subdir() {
|
||||
let mut correct = HashSet::new();
|
||||
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
||||
assert_eq!(simplify_dir_names(vec!["a/b/c", "a/b", "a/b/d/f"]), correct);
|
||||
assert_eq!(simplify_dir_names(vec!["a/b", "a/b/c", "a/b/d/f"]), correct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_dir_duplicates() {
|
||||
let mut correct = HashSet::new();
|
||||
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
||||
correct.insert(PathBuf::from("c"));
|
||||
assert_eq!(
|
||||
simplify_dir_names(vec![
|
||||
"a/b",
|
||||
"a/b//",
|
||||
"a/././b///",
|
||||
"c",
|
||||
"c/",
|
||||
"c/.",
|
||||
"c/././",
|
||||
"c/././."
|
||||
]),
|
||||
correct
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_simplify_dir_rm_subdir_and_not_substrings() {
|
||||
let mut correct = HashSet::new();
|
||||
correct.insert(PathBuf::from("b"));
|
||||
correct.insert(["c", "a", "b"].iter().collect::<PathBuf>());
|
||||
correct.insert(["a", "b"].iter().collect::<PathBuf>());
|
||||
assert_eq!(simplify_dir_names(vec!["a/b", "c/a/b/", "b"]), correct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_dir_dots() {
|
||||
let mut correct = HashSet::new();
|
||||
correct.insert(PathBuf::from("src"));
|
||||
assert_eq!(simplify_dir_names(vec!["src/."]), correct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_dir_substring_names() {
|
||||
let mut correct = HashSet::new();
|
||||
correct.insert(PathBuf::from("src"));
|
||||
correct.insert(PathBuf::from("src_v2"));
|
||||
assert_eq!(simplify_dir_names(vec!["src/", "src_v2"]), correct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_a_parent_of() {
|
||||
assert!(is_a_parent_of("/usr", "/usr/andy"));
|
||||
assert!(is_a_parent_of("/usr", "/usr/andy/i/am/descendant"));
|
||||
assert!(!is_a_parent_of("/usr", "/usr/."));
|
||||
assert!(!is_a_parent_of("/usr", "/usr/"));
|
||||
assert!(!is_a_parent_of("/usr", "/usr"));
|
||||
assert!(!is_a_parent_of("/usr/", "/usr"));
|
||||
assert!(!is_a_parent_of("/usr/andy", "/usr"));
|
||||
assert!(!is_a_parent_of("/usr/andy", "/usr/sibling"));
|
||||
assert!(!is_a_parent_of("/usr/folder", "/usr/folder_not_a_child"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_a_parent_of_root() {
|
||||
assert!(is_a_parent_of("/", "/usr/andy"));
|
||||
assert!(is_a_parent_of("/", "/usr"));
|
||||
assert!(!is_a_parent_of("/", "/"));
|
||||
}
|
||||
}
|
||||
363
src/utils/mod.rs
363
src/utils/mod.rs
@@ -1,363 +0,0 @@
|
||||
use jwalk::DirEntry;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use jwalk::WalkDir;
|
||||
|
||||
mod platform;
|
||||
use self::platform::*;
|
||||
|
||||
#[derive(Debug, Default, Eq)]
|
||||
pub struct Node {
|
||||
pub name: String,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_a_parent_of(parent: &str, child: &str) -> bool {
|
||||
let path_parent = std::path::Path::new(parent);
|
||||
let path_child = std::path::Path::new(child);
|
||||
(path_child.starts_with(path_parent) && !path_parent.starts_with(path_child))
|
||||
}
|
||||
|
||||
pub fn simplify_dir_names(filenames: Vec<&str>) -> HashSet<String> {
|
||||
let mut top_level_names: HashSet<String> = HashSet::with_capacity(filenames.len());
|
||||
let mut to_remove: Vec<String> = 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_string());
|
||||
} 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(normalize_path(t).to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
top_level_names
|
||||
}
|
||||
|
||||
pub fn get_dir_tree(
|
||||
top_level_names: &HashSet<String>,
|
||||
ignore_directories: Option<Vec<&str>>,
|
||||
apparent_size: bool,
|
||||
limit_filesystem: bool,
|
||||
threads: Option<usize>,
|
||||
) -> (bool, HashMap<String, u64>) {
|
||||
let mut permissions = 0;
|
||||
let mut data: HashMap<String, u64> = HashMap::new();
|
||||
let restricted_filesystems = if limit_filesystem {
|
||||
get_allowed_filesystems(top_level_names)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for b in top_level_names.iter() {
|
||||
examine_dir(
|
||||
&b,
|
||||
apparent_size,
|
||||
&restricted_filesystems,
|
||||
&ignore_directories,
|
||||
&mut data,
|
||||
&mut permissions,
|
||||
threads,
|
||||
);
|
||||
}
|
||||
(permissions == 0, data)
|
||||
}
|
||||
|
||||
fn get_allowed_filesystems(top_level_names: &HashSet<String>) -> Option<HashSet<u64>> {
|
||||
let mut limit_filesystems: HashSet<u64> = HashSet::new();
|
||||
for file_name in top_level_names.iter() {
|
||||
if let Ok(a) = get_filesystem(file_name) {
|
||||
limit_filesystems.insert(a);
|
||||
}
|
||||
}
|
||||
Some(limit_filesystems)
|
||||
}
|
||||
|
||||
pub fn normalize_path<P: AsRef<std::path::Path>>(path: P) -> std::string::String {
|
||||
// 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::<std::path::PathBuf>()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn examine_dir(
|
||||
top_dir: &str,
|
||||
apparent_size: bool,
|
||||
filesystems: &Option<HashSet<u64>>,
|
||||
ignore_directories: &Option<Vec<&str>>,
|
||||
data: &mut HashMap<String, u64>,
|
||||
file_count_no_permission: &mut u64,
|
||||
threads: Option<usize>,
|
||||
) {
|
||||
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
|
||||
let mut iter = WalkDir::new(top_dir)
|
||||
.preload_metadata(true)
|
||||
.skip_hidden(false);
|
||||
if let Some(threads_to_start) = threads {
|
||||
iter = iter.num_threads(threads_to_start);
|
||||
}
|
||||
'entry: for entry in iter {
|
||||
if let Ok(e) = entry {
|
||||
let maybe_size_and_inode = get_metadata(&e, apparent_size);
|
||||
if let Some(d) = ignore_directories {
|
||||
for s in d {
|
||||
if e.path().to_string_lossy().contains(*s) {
|
||||
continue 'entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match maybe_size_and_inode {
|
||||
Some((size, maybe_inode)) => {
|
||||
if !should_ignore_file(apparent_size, filesystems, &mut inodes, maybe_inode) {
|
||||
process_file_with_size_and_inode(top_dir, data, e, size)
|
||||
}
|
||||
}
|
||||
None => *file_count_no_permission += 1,
|
||||
}
|
||||
} else {
|
||||
*file_count_no_permission += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn should_ignore_file(
|
||||
apparent_size: bool,
|
||||
restricted_filesystems: &Option<HashSet<u64>>,
|
||||
inodes: &mut HashSet<(u64, u64)>,
|
||||
maybe_inode: Option<(u64, u64)>,
|
||||
) -> bool {
|
||||
if !apparent_size {
|
||||
if let Some(inode_dev_pair) = maybe_inode {
|
||||
// Ignore files on different devices (if flag applied)
|
||||
if restricted_filesystems.is_some()
|
||||
&& !restricted_filesystems
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&inode_dev_pair.1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Ignore files already visited or symlinked
|
||||
if inodes.contains(&inode_dev_pair) {
|
||||
return true;
|
||||
}
|
||||
inodes.insert(inode_dev_pair);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn process_file_with_size_and_inode(
|
||||
top_dir: &str,
|
||||
data: &mut HashMap<String, u64>,
|
||||
e: DirEntry,
|
||||
size: u64,
|
||||
) {
|
||||
// This path and all its parent paths have their counter incremented
|
||||
for path_name in e.path().ancestors() {
|
||||
// This is required due to bug in Jwalk that adds '/' to all sub dir lists
|
||||
// see: https://github.com/jessegrosjean/jwalk/issues/13
|
||||
if path_name.to_string_lossy() == "/" && top_dir != "/" {
|
||||
continue;
|
||||
}
|
||||
let path_name = path_name.to_string_lossy();
|
||||
let s = data.entry(path_name.to_string()).or_insert(0);
|
||||
*s += size;
|
||||
if path_name == top_dir {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort_by_size_first_name_second(a: &(String, u64), b: &(String, 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<String, u64>) -> Vec<(String, u64)> {
|
||||
let mut new_l: Vec<(String, 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<(String, u64)>, max_to_show: usize) -> Vec<(String, u64)> {
|
||||
if max_to_show > 0 && new_l.len() > max_to_show {
|
||||
new_l[0..max_to_show].to_vec()
|
||||
} else {
|
||||
new_l
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim_deep_ones(
|
||||
input: Vec<(String, u64)>,
|
||||
max_depth: u64,
|
||||
top_level_names: &HashSet<String>,
|
||||
) -> Vec<(String, u64)> {
|
||||
let mut result: Vec<(String, u64)> = Vec::with_capacity(input.len() * top_level_names.len());
|
||||
|
||||
for name in top_level_names {
|
||||
let my_max_depth = name.matches(std::path::is_separator).count() + max_depth as usize;
|
||||
let name_ref: &str = name.as_ref();
|
||||
|
||||
for &(ref k, ref v) in input.iter() {
|
||||
if k.starts_with(name_ref) && k.matches(std::path::is_separator).count() <= my_max_depth
|
||||
{
|
||||
result.push((k.clone(), *v));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_simplify_dir() {
|
||||
let mut correct = HashSet::new();
|
||||
correct.insert("a".to_string());
|
||||
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::<std::path::PathBuf>()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
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::<std::path::PathBuf>()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
correct.insert("c".to_string());
|
||||
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("b".to_string());
|
||||
correct.insert(
|
||||
["c", "a", "b"]
|
||||
.iter()
|
||||
.collect::<std::path::PathBuf>()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
correct.insert(
|
||||
["a", "b"]
|
||||
.iter()
|
||||
.collect::<std::path::PathBuf>()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
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("src".to_string());
|
||||
assert_eq!(simplify_dir_names(vec!["src/."]), correct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_dir_substring_names() {
|
||||
let mut correct = HashSet::new();
|
||||
correct.insert("src".to_string());
|
||||
correct.insert("src_v2".to_string());
|
||||
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("/", "/"));
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
use jwalk::DirEntry;
|
||||
#[allow(unused_imports)]
|
||||
use std::fs;
|
||||
use std::io;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
fn get_block_size() -> u64 {
|
||||
// All os specific implementations of MetatdataExt seem to define a block as 512 bytes
|
||||
// https://doc.rust-lang.org/std/os/linux/fs/trait.MetadataExt.html#tymethod.st_blocks
|
||||
512
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
d.metadata.as_ref().unwrap().as_ref().ok().map(|md| {
|
||||
let inode = Some((md.ino(), md.dev()));
|
||||
if use_apparent_size {
|
||||
(md.len(), inode)
|
||||
} else {
|
||||
(md.blocks() * get_block_size(), inode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
pub fn get_metadata(d: &DirEntry, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
||||
use winapi_util::file::information;
|
||||
use winapi_util::Handle;
|
||||
|
||||
let h = Handle::from_path_any(d.path()).ok()?;
|
||||
let info = information(&h).ok()?;
|
||||
|
||||
Some((
|
||||
info.file_size(),
|
||||
Some((info.file_index(), info.volume_serial_number())),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_family = "windows"), not(target_family = "unix")))]
|
||||
pub fn get_metadata(d: &DirEntry, _apparent: bool) -> Option<(u64, Option<(u64, u64)>)> {
|
||||
d.metadata
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.ok()
|
||||
.map(|md| (md.len(), None))
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn get_filesystem(file_path: &str) -> Result<u64, io::Error> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let metadata = fs::metadata(file_path)?;
|
||||
Ok(metadata.dev())
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
pub fn get_filesystem(file_path: &str) -> Result<u64, io::Error> {
|
||||
use winapi_util::file::information;
|
||||
use winapi_util::Handle;
|
||||
|
||||
let h = Handle::from_path_any(file_path)?;
|
||||
let info = information(&h)?;
|
||||
Ok(info.volume_serial_number())
|
||||
}
|
||||
1
tests/test_dir_hidden_entries/.hidden_file
Normal file
1
tests/test_dir_hidden_entries/.hidden_file
Normal file
@@ -0,0 +1 @@
|
||||
hi
|
||||
0
tests/test_dir_unicode/ラウトは難しいです!.japan
Normal file
0
tests/test_dir_unicode/ラウトは難しいです!.japan
Normal file
0
tests/test_dir_unicode/👩.unicode
Normal file
0
tests/test_dir_unicode/👩.unicode
Normal file
208
tests/test_exact_output.rs
Normal file
208
tests/test_exact_output.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
use assert_cmd::Command;
|
||||
use std::ffi::OsStr;
|
||||
use std::str;
|
||||
use std::sync::Once;
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
/**
|
||||
* This file contains tests that verify the exact output of the command.
|
||||
* This output differs on Linux / Mac so the tests are harder to write and debug
|
||||
* Windows is ignored here because the results vary by host making exact testing impractical
|
||||
*
|
||||
* Despite the above problems, these tests are good as they are the closest to 'the real thing'.
|
||||
*/
|
||||
|
||||
// Warning: File sizes differ on both platform and on the format of the disk.
|
||||
/// Copy to /tmp dir - we assume that the formatting of the /tmp partition
|
||||
/// is consistent. If the tests fail your /tmp filesystem probably differs
|
||||
fn copy_test_data(dir: &str) {
|
||||
// First remove the existing directory - just in case it is there and has incorrect data
|
||||
let last_slash = dir.rfind('/').unwrap();
|
||||
let last_part_of_dir = dir.chars().skip(last_slash).collect::<String>();
|
||||
let _ = Command::new("rm")
|
||||
.arg("-rf")
|
||||
.arg("/tmp/".to_owned() + &*last_part_of_dir)
|
||||
.ok();
|
||||
|
||||
let _ = Command::new("cp")
|
||||
.arg("-r")
|
||||
.arg(dir)
|
||||
.arg("/tmp/")
|
||||
.ok()
|
||||
.map_err(|err| eprintln!("Error copying directory for test setup\n{:?}", err));
|
||||
}
|
||||
|
||||
fn initialize() {
|
||||
INIT.call_once(|| {
|
||||
copy_test_data("tests/test_dir");
|
||||
copy_test_data("tests/test_dir2");
|
||||
copy_test_data("tests/test_dir_unicode");
|
||||
});
|
||||
}
|
||||
|
||||
fn exact_output_test<T: AsRef<OsStr>>(valid_outputs: Vec<String>, command_args: Vec<T>) {
|
||||
initialize();
|
||||
|
||||
let mut a = &mut Command::cargo_bin("dust").unwrap();
|
||||
|
||||
for p in command_args {
|
||||
a = a.arg(p);
|
||||
}
|
||||
|
||||
let output = str::from_utf8(&a.unwrap().stdout).unwrap().to_owned();
|
||||
|
||||
assert!(valid_outputs.iter().any(|i| output.contains(i)));
|
||||
}
|
||||
|
||||
// "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_main_basic() {
|
||||
// -c is no color mode - This makes testing much simpler
|
||||
exact_output_test(main_output(), vec!["-c", "/tmp/test_dir/"])
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_main_multi_arg() {
|
||||
let command_args = vec![
|
||||
"-c",
|
||||
"/tmp/test_dir/many/",
|
||||
"/tmp/test_dir",
|
||||
"/tmp/test_dir",
|
||||
];
|
||||
exact_output_test(main_output(), command_args);
|
||||
}
|
||||
|
||||
fn main_output() -> Vec<String> {
|
||||
// Some linux currently thought to be Manjaro, Arch
|
||||
// Although probably depends on how drive is formatted
|
||||
let mac_and_some_linux = r#"
|
||||
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||
4.0K ├── hello_file│█████████████████████████████████████████████████ │ 100%
|
||||
4.0K ┌─┴ many │█████████████████████████████████████████████████ │ 100%
|
||||
4.0K ┌─┴ test_dir │█████████████████████████████████████████████████ │ 100%
|
||||
"#
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let ubuntu = r#"
|
||||
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33%
|
||||
8.0K ┌─┴ many │ █████████████████████████████████ │ 67%
|
||||
12K ┌─┴ test_dir │█████████████████████████████████████████████████ │ 100%
|
||||
"#
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
vec![mac_and_some_linux, ubuntu]
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_main_long_paths() {
|
||||
let command_args = vec!["-c", "-p", "/tmp/test_dir/"];
|
||||
exact_output_test(main_output_long_paths(), command_args);
|
||||
}
|
||||
|
||||
fn main_output_long_paths() -> Vec<String> {
|
||||
let mac_and_some_linux = r#"
|
||||
0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||
4.0K ├── /tmp/test_dir/many/hello_file│██████████████████████████████ │ 100%
|
||||
4.0K ┌─┴ /tmp/test_dir/many │██████████████████████████████ │ 100%
|
||||
4.0K ┌─┴ /tmp/test_dir │██████████████████████████████ │ 100%
|
||||
"#
|
||||
.trim()
|
||||
.to_string();
|
||||
let ubuntu = r#"
|
||||
0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░░█ │ 0%
|
||||
4.0K ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░███████████ │ 33%
|
||||
8.0K ┌─┴ /tmp/test_dir/many │ █████████████████████ │ 67%
|
||||
12K ┌─┴ /tmp/test_dir │██████████████████████████████ │ 100%
|
||||
"#
|
||||
.trim()
|
||||
.to_string();
|
||||
vec![mac_and_some_linux, ubuntu]
|
||||
}
|
||||
|
||||
// Check against directories and files whose names are substrings of each other
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_substring_of_names_and_long_names() {
|
||||
let command_args = vec!["-c", "/tmp/test_dir2"];
|
||||
exact_output_test(no_substring_of_names_output(), command_args);
|
||||
}
|
||||
|
||||
fn no_substring_of_names_output() -> Vec<String> {
|
||||
let ubuntu = "
|
||||
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes..
|
||||
4.0K ├── dir_name_clash
|
||||
4.0K │ ┌── hello
|
||||
8.0K ├─┴ dir
|
||||
4.0K │ ┌── hello
|
||||
8.0K ├─┴ dir_substring
|
||||
24K ┌─┴ test_dir2
|
||||
"
|
||||
.trim()
|
||||
.into();
|
||||
|
||||
let mac_and_some_linux = "
|
||||
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes..
|
||||
4.0K │ ┌── hello
|
||||
4.0K ├─┴ dir
|
||||
4.0K ├── dir_name_clash
|
||||
4.0K │ ┌── hello
|
||||
4.0K ├─┴ dir_substring
|
||||
12K ┌─┴ test_dir2
|
||||
"
|
||||
.trim()
|
||||
.into();
|
||||
vec![mac_and_some_linux, ubuntu]
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_unicode_directories() {
|
||||
let command_args = vec!["-c", "/tmp/test_dir_unicode"];
|
||||
exact_output_test(unicode_dir(), command_args);
|
||||
}
|
||||
|
||||
fn unicode_dir() -> Vec<String> {
|
||||
// The way unicode & asian characters are rendered on the terminal should make this line up
|
||||
let ubuntu = "
|
||||
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
|
||||
0B ├── 👩.unicode │ █ │ 0%
|
||||
4.0K ┌─┴ test_dir_unicode │███████████████████████████████████ │ 100%
|
||||
"
|
||||
.trim()
|
||||
.into();
|
||||
|
||||
let mac_and_some_linux = "
|
||||
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
|
||||
0B ├── 👩.unicode │ █ │ 0%
|
||||
0B ┌─┴ test_dir_unicode │ █ │ 0%
|
||||
"
|
||||
.trim()
|
||||
.into();
|
||||
vec![mac_and_some_linux, ubuntu]
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_apparent_size() {
|
||||
let command_args = vec!["-c", "-s", "-b", "/tmp/test_dir"];
|
||||
exact_output_test(apparent_size_output(), command_args);
|
||||
}
|
||||
|
||||
fn apparent_size_output() -> Vec<String> {
|
||||
// The apparent directory sizes are too unpredictable and system dependent to try and match
|
||||
let files = r#"
|
||||
0B ┌── a_file
|
||||
6B ├── hello_file
|
||||
"#
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
vec![files]
|
||||
}
|
||||
195
tests/test_flags.rs
Normal file
195
tests/test_flags.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use assert_cmd::Command;
|
||||
use std::ffi::OsStr;
|
||||
use std::str;
|
||||
|
||||
/**
|
||||
* This file contains tests that test a substring of the output using '.contains'
|
||||
*
|
||||
* These tests should be the same cross platform
|
||||
*/
|
||||
|
||||
fn build_command<T: AsRef<OsStr>>(command_args: Vec<T>) -> String {
|
||||
let mut cmd = &mut Command::cargo_bin("dust").unwrap();
|
||||
for p in command_args {
|
||||
cmd = cmd.arg(p);
|
||||
}
|
||||
let finished = &cmd.unwrap();
|
||||
let stderr = str::from_utf8(&finished.stderr).unwrap();
|
||||
assert_eq!(stderr, "");
|
||||
|
||||
str::from_utf8(&finished.stdout).unwrap().into()
|
||||
}
|
||||
|
||||
// We can at least test the file names are there
|
||||
#[test]
|
||||
pub fn test_basic_output() {
|
||||
let output = build_command(vec!["tests/test_dir/"]);
|
||||
|
||||
assert!(output.contains(" ┌─┴ "));
|
||||
assert!(output.contains("test_dir "));
|
||||
assert!(output.contains(" ┌─┴ "));
|
||||
assert!(output.contains("many "));
|
||||
assert!(output.contains(" ├── "));
|
||||
assert!(output.contains("hello_file"));
|
||||
assert!(output.contains(" ┌── "));
|
||||
assert!(output.contains("a_file "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_output_no_bars_means_no_excess_spaces() {
|
||||
let output = build_command(vec!["-b", "tests/test_dir/"]);
|
||||
// If bars are not being shown we don't need to pad the output with spaces
|
||||
assert!(output.contains("many"));
|
||||
assert!(!output.contains("many "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_reverse_flag() {
|
||||
let output = build_command(vec!["-r", "-c", "tests/test_dir/"]);
|
||||
assert!(output.contains(" └─┬ test_dir "));
|
||||
assert!(output.contains(" └─┬ many "));
|
||||
assert!(output.contains(" ├── hello_file"));
|
||||
assert!(output.contains(" └── a_file "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_d_flag_works() {
|
||||
// We should see the top level directory but not the sub dirs / files:
|
||||
let output = build_command(vec!["-d", "1", "tests/test_dir/"]);
|
||||
assert!(!output.contains("hello_file"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_d_flag_works_and_still_recurses_down() {
|
||||
// We had a bug where running with '-d 1' would stop at the first directory and the code
|
||||
// would fail to recurse down
|
||||
let output = build_command(vec!["-d", "1", "-f", "-c", "tests/test_dir2/"]);
|
||||
assert!(output.contains("4 ┌─┴ test_dir2"));
|
||||
}
|
||||
|
||||
// Check against directories and files whose names are substrings of each other
|
||||
#[test]
|
||||
pub fn test_ignore_dir() {
|
||||
let output = build_command(vec!["-c", "-X", "dir_substring", "tests/test_dir2/"]);
|
||||
assert!(!output.contains("dir_substring"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_with_bad_param() {
|
||||
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||
let result = cmd.arg("bad_place").unwrap();
|
||||
let stderr = str::from_utf8(&result.stderr).unwrap();
|
||||
assert!(stderr.contains("Did not have permissions for all directories"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_hidden_flag() {
|
||||
// Check we can see the hidden file normally
|
||||
let output = build_command(vec!["-c", "tests/test_dir_hidden_entries/"]);
|
||||
assert!(output.contains(".hidden_file"));
|
||||
assert!(output.contains("┌─┴ test_dir_hidden_entries"));
|
||||
|
||||
// Check that adding the '-h' flag causes us to not see hidden files
|
||||
let output = build_command(vec!["-c", "-i", "tests/test_dir_hidden_entries/"]);
|
||||
assert!(!output.contains(".hidden_file"));
|
||||
assert!(output.contains("┌── test_dir_hidden_entries"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_number_of_files() {
|
||||
// Check we can see the hidden file normally
|
||||
let output = build_command(vec!["-c", "-f", "tests/test_dir"]);
|
||||
assert!(output.contains("1 ┌── a_file "));
|
||||
assert!(output.contains("1 ├── hello_file"));
|
||||
assert!(output.contains("2 ┌─┴ many"));
|
||||
assert!(output.contains("2 ┌─┴ test_dir"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_files_by_type() {
|
||||
// Check we can list files by type
|
||||
let output = build_command(vec!["-c", "-t", "tests"]);
|
||||
assert!(output.contains(" .unicode"));
|
||||
assert!(output.contains(" .japan"));
|
||||
assert!(output.contains(" .rs"));
|
||||
assert!(output.contains(" (no extension)"));
|
||||
assert!(output.contains("┌─┴ (total)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_output_skip_total() {
|
||||
let output = build_command(vec![
|
||||
"--skip-total",
|
||||
"tests/test_dir/many/hello_file",
|
||||
"tests/test_dir/many/a_file",
|
||||
]);
|
||||
assert!(output.contains("hello_file"));
|
||||
assert!(!output.contains("(total)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_files_by_regex_match_lots() {
|
||||
// Check we can see '.rs' files in the tests directory
|
||||
let output = build_command(vec!["-c", "-e", "\\.rs$", "tests"]);
|
||||
assert!(output.contains(" ┌─┴ tests"));
|
||||
assert!(!output.contains("0B ┌── tests"));
|
||||
assert!(!output.contains("0B ┌─┴ tests"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_files_by_regex_match_nothing() {
|
||||
// Check there are no files named: '.match_nothing' in the tests directory
|
||||
let output = build_command(vec!["-c", "-e", "match_nothing$", "tests"]);
|
||||
assert!(output.contains("0B ┌── tests"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_files_by_regex_match_multiple() {
|
||||
let output = build_command(vec![
|
||||
"-c",
|
||||
"-e",
|
||||
"test_dir_hidden",
|
||||
"-e",
|
||||
"test_dir2",
|
||||
"-n",
|
||||
"100",
|
||||
"tests",
|
||||
]);
|
||||
assert!(output.contains("test_dir2"));
|
||||
assert!(output.contains("test_dir_hidden"));
|
||||
assert!(!output.contains("many")); // We do not find the 'many' folder in the 'test_dir' folder
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_files_by_invert_regex() {
|
||||
let output = build_command(vec!["-c", "-f", "-v", "e", "tests/test_dir2"]);
|
||||
// There are 0 files without 'e' in the name
|
||||
assert!(output.contains("0 ┌── test_dir2"));
|
||||
|
||||
let output = build_command(vec!["-c", "-f", "-v", "a", "tests/test_dir2"]);
|
||||
// There are 2 files without 'a' in the name
|
||||
assert!(output.contains("2 ┌─┴ test_dir2"));
|
||||
|
||||
// There are 4 files in the test_dir2 hierarchy
|
||||
let output = build_command(vec!["-c", "-f", "-v", "match_nothing$", "tests/test_dir2"]);
|
||||
assert!(output.contains("4 ┌─┴ test_dir2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_show_files_by_invert_regex_match_multiple() {
|
||||
// We ignore test_dir2 & test_dir_unicode, leaving the test_dir folder
|
||||
// which has the 'many' folder inside
|
||||
let output = build_command(vec![
|
||||
"-c",
|
||||
"-v",
|
||||
"test_dir2",
|
||||
"-v",
|
||||
"test_dir_unicode",
|
||||
"-n",
|
||||
"100",
|
||||
"tests",
|
||||
]);
|
||||
assert!(!output.contains("test_dir2"));
|
||||
assert!(!output.contains("test_dir_unicode"));
|
||||
assert!(output.contains("many"));
|
||||
}
|
||||
1
tests/tests.rs
Normal file
1
tests/tests.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
118
tests/tests_symlinks.rs
Normal file
118
tests/tests_symlinks.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use assert_cmd::Command;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
|
||||
use tempfile::Builder;
|
||||
use tempfile::TempDir;
|
||||
|
||||
// File sizes differ on both platform and on the format of the disk.
|
||||
// Windows: `ln` is not usually an available command; creation of symbolic links requires special enhanced permissions
|
||||
|
||||
fn build_temp_file(dir: &TempDir) -> PathBuf {
|
||||
let file_path = dir.path().join("notes.txt");
|
||||
let mut file = File::create(&file_path).unwrap();
|
||||
writeln!(file, "I am a temp file").unwrap();
|
||||
file_path
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_soft_sym_link() {
|
||||
let dir = Builder::new().tempdir().unwrap();
|
||||
let file = build_temp_file(&dir);
|
||||
let dir_s = dir.path().to_str().unwrap();
|
||||
let file_path_s = file.to_str().unwrap();
|
||||
|
||||
let link_name = dir.path().join("the_link");
|
||||
let link_name_s = link_name.to_str().unwrap();
|
||||
let c = Command::new("ln")
|
||||
.arg("-s")
|
||||
.arg(file_path_s)
|
||||
.arg(link_name_s)
|
||||
.output();
|
||||
assert!(c.is_ok());
|
||||
|
||||
let c = format!(" ├── {}", link_name_s);
|
||||
let b = format!(" ┌── {}", file_path_s);
|
||||
let a = format!("─┴ {}", dir_s);
|
||||
|
||||
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||
// Mac test runners create long filenames in tmp directories
|
||||
let output = cmd
|
||||
.args(["-p", "-c", "-s", "-w 999", dir_s])
|
||||
.unwrap()
|
||||
.stdout;
|
||||
|
||||
let output = str::from_utf8(&output).unwrap();
|
||||
|
||||
assert!(output.contains(a.as_str()));
|
||||
assert!(output.contains(b.as_str()));
|
||||
assert!(output.contains(c.as_str()));
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_hard_sym_link() {
|
||||
let dir = Builder::new().tempdir().unwrap();
|
||||
let file = build_temp_file(&dir);
|
||||
let dir_s = dir.path().to_str().unwrap();
|
||||
let file_path_s = file.to_str().unwrap();
|
||||
|
||||
let link_name = dir.path().join("the_link");
|
||||
let link_name_s = link_name.to_str().unwrap();
|
||||
let c = Command::new("ln")
|
||||
.arg(file_path_s)
|
||||
.arg(link_name_s)
|
||||
.output();
|
||||
assert!(c.is_ok());
|
||||
|
||||
let file_output = format!(" ┌── {}", file_path_s);
|
||||
let dirs_output = format!("─┴ {}", dir_s);
|
||||
|
||||
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||
// Mac test runners create long filenames in tmp directories
|
||||
let output = cmd.args(["-p", "-c", "-w 999", dir_s]).unwrap().stdout;
|
||||
|
||||
// The link should not appear in the output because multiple inodes are now ordered
|
||||
// then filtered.
|
||||
let output = str::from_utf8(&output).unwrap();
|
||||
assert!(output.contains(dirs_output.as_str()));
|
||||
assert!(output.contains(file_output.as_str()));
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "windows", ignore)]
|
||||
#[test]
|
||||
pub fn test_recursive_sym_link() {
|
||||
let dir = Builder::new().tempdir().unwrap();
|
||||
let dir_s = dir.path().to_str().unwrap();
|
||||
|
||||
let link_name = dir.path().join("the_link");
|
||||
let link_name_s = link_name.to_str().unwrap();
|
||||
|
||||
let c = Command::new("ln")
|
||||
.arg("-s")
|
||||
.arg(dir_s)
|
||||
.arg(link_name_s)
|
||||
.output();
|
||||
assert!(c.is_ok());
|
||||
|
||||
let a = format!("─┬ {}", dir_s);
|
||||
let b = format!(" └── {}", link_name_s);
|
||||
|
||||
let mut cmd = Command::cargo_bin("dust").unwrap();
|
||||
let output = cmd
|
||||
.arg("-p")
|
||||
.arg("-c")
|
||||
.arg("-r")
|
||||
.arg("-s")
|
||||
.arg("-w 999")
|
||||
.arg(dir_s)
|
||||
.unwrap()
|
||||
.stdout;
|
||||
let output = str::from_utf8(&output).unwrap();
|
||||
|
||||
assert!(output.contains(a.as_str()));
|
||||
assert!(output.contains(b.as_str()));
|
||||
}
|
||||
Reference in New Issue
Block a user