From 40667bcd076822cb65efd83bdcf6168439aebe45 Mon Sep 17 00:00:00 2001 From: Olivier 'reivilibre Date: Sun, 23 Apr 2023 17:05:15 +0100 Subject: [PATCH] RIP OFF the dust display style: This looks like a lot of removals, but in fact I just started from a blank slate and only added what I wanted. --- .github/workflows/CICD.yml | 347 ---------------- .gitignore | 13 +- Cargo.lock | 376 +----------------- Cargo.toml | 63 +-- LICENSE => LICENCE | 0 README.md | 102 +---- build.rs | 27 -- ci/before_deploy.ps1 | 23 -- ci/before_deploy.sh | 34 -- ci/how2publish.txt | 14 - ci/install.sh | 28 -- ci/script.sh | 25 -- completions/_dust | 77 ---- completions/_dust.ps1 | 79 ---- completions/dust.bash | 94 ----- completions/dust.elv | 73 ---- completions/dust.fish | 25 -- config/config.toml | 28 -- man-page/dust.1 | 90 ----- media/snap.png | Bin 62704 -> 0 bytes shell.nix | 19 + src/cli.rs | 172 -------- src/config.rs | 215 ---------- src/dir_walker.rs | 229 ----------- src/lib.rs | 178 +++++++++ src/main.rs | 257 ------------ src/node.rs | 46 --- src/platform.rs | 128 ------ src/utils.rs | 13 - tests/test_dir/many/a_file | 0 tests/test_dir/many/hello_file | 1 - tests/test_dir2/dir/hello | 1 - tests/test_dir2/dir_name_clash | 1 - tests/test_dir2/dir_substring/hello | 1 - ...when_this_goes_over_80_characters_i_wonder | 0 tests/test_dir_hidden_entries/.hidden_file | 1 - .../ラウトは難しいです!.japan | 0 tests/test_dir_unicode/👩.unicode | 0 tests/test_exact_output.rs | 208 ---------- tests/test_flags.rs | 222 ----------- tests/tests.rs | 1 - tests/tests_symlinks.rs | 141 ------- 42 files changed, 215 insertions(+), 3137 deletions(-) delete mode 100644 .github/workflows/CICD.yml rename LICENSE => LICENCE (100%) delete mode 100644 build.rs delete mode 100644 ci/before_deploy.ps1 delete mode 100644 ci/before_deploy.sh delete mode 100644 ci/how2publish.txt delete mode 100644 ci/install.sh delete mode 100644 ci/script.sh delete mode 100644 completions/_dust delete mode 100644 completions/_dust.ps1 delete mode 100644 completions/dust.bash delete mode 100644 completions/dust.elv delete mode 100644 completions/dust.fish delete mode 100644 config/config.toml delete mode 100644 man-page/dust.1 delete mode 100644 media/snap.png create mode 100644 shell.nix delete mode 100644 src/cli.rs delete mode 100644 src/config.rs delete mode 100644 src/dir_walker.rs create mode 100644 src/lib.rs delete mode 100644 src/main.rs delete mode 100644 src/platform.rs delete mode 100644 tests/test_dir/many/a_file delete mode 100644 tests/test_dir/many/hello_file delete mode 100644 tests/test_dir2/dir/hello delete mode 100644 tests/test_dir2/dir_name_clash delete mode 100644 tests/test_dir2/dir_substring/hello delete mode 100644 tests/test_dir2/long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes_over_80_characters_i_wonder delete mode 100644 tests/test_dir_hidden_entries/.hidden_file delete mode 100644 tests/test_dir_unicode/ラウトは難しいです!.japan delete mode 100644 tests/test_dir_unicode/👩.unicode delete mode 100644 tests/test_exact_output.rs delete mode 100644 tests/test_flags.rs delete mode 100644 tests/tests.rs delete mode 100644 tests/tests_symlinks.rs diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml deleted file mode 100644 index d1d9824..0000000 --- a/.github/workflows/CICD.yml +++ /dev/null @@ -1,347 +0,0 @@ -name: CICD - -# spell-checker:ignore CICD CODECOV MSVC MacOS Peltoche SHAs buildable clippy esac fakeroot gnueabihf halium libssl mkdir musl popd printf pushd rustfmt softprops toolchain - -env: - PROJECT_NAME: dust - PROJECT_DESC: "du + rust = dust" - PROJECT_AUTH: "bootandy" - RUST_MIN_SRV: "1.31.0" - -on: [push, pull_request] - -jobs: - style: - name: Style - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { os: ubuntu-latest } - - { os: macos-latest } - - { os: windows-latest } - steps: - - uses: actions/checkout@v1 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - # 'windows-latest' `cargo fmt` is bugged for this project (see reasons @ GH:rust-lang/rustfmt #3324, #3590, #3688 ; waiting for repair) - JOB_DO_FORMAT_TESTING="true" - case ${{ matrix.job.os }} in windows-latest) unset JOB_DO_FORMAT_TESTING ;; esac; - echo set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING:-/false} - echo ::set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING} - # target-specific options - # * CARGO_FEATURES_OPTION - CARGO_FEATURES_OPTION='' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi - echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} - echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION} - - name: Install `rust` toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - profile: minimal # minimal component installation (ie, no documentation) - components: rustfmt, clippy - - name: "`fmt` testing" - if: steps.vars.outputs.JOB_DO_FORMAT_TESTING - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - name: "`clippy` testing" - if: success() || failure() # run regardless of prior step ("`fmt` testing") success/failure - uses: actions-rs/cargo@v1 - with: - command: clippy - args: ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings - - min_version: - name: MinSRV # Minimum supported rust version - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ env.RUST_MIN_SRV }} - profile: minimal # minimal component installation (ie, no documentation) - - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - - build: - name: Build - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - # { os, target, cargo-options, features, use-cross, toolchain } - - { - os: ubuntu-latest, - target: aarch64-unknown-linux-gnu, - use-cross: use-cross, - } - - { - os: ubuntu-latest, - target: aarch64-unknown-linux-musl, - use-cross: use-cross, - } - - { - os: ubuntu-latest, - target: arm-unknown-linux-gnueabihf, - use-cross: use-cross, - } - - { - os: ubuntu-latest, - target: i686-unknown-linux-gnu, - use-cross: use-cross, - } - - { - os: ubuntu-latest, - target: i686-unknown-linux-musl, - use-cross: use-cross, - } - - { - os: ubuntu-latest, - target: x86_64-unknown-linux-gnu, - use-cross: use-cross, - } - - { - os: ubuntu-latest, - target: x86_64-unknown-linux-musl, - use-cross: use-cross, - } - - { os: macos-latest, target: x86_64-apple-darwin } - - { os: windows-latest, target: i686-pc-windows-gnu } - - { os: windows-latest, target: i686-pc-windows-msvc } - - { os: windows-latest, target: x86_64-pc-windows-gnu } ## !maint: [rivy; 2020-01-21] may break due to rust bug; follow possible solution from GH:rust-lang/rust#47048 (refs: GH:rust-lang/rust#47048 , GH:rust-lang/rust#53454 , GH:bike-barn/hermit#172 ) - - { os: windows-latest, target: x86_64-pc-windows-msvc } - steps: - - uses: actions/checkout@v1 - - name: Install any prerequisites - shell: bash - run: | - case ${{ matrix.job.target }} in - arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; - aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install binutils-aarch64-linux-gnu ;; - esac - - name: Initialize workflow variables - id: vars - shell: bash - run: | - # toolchain - TOOLCHAIN="stable" ## default to "stable" toolchain - # * specify alternate TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: , , ) - 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:-/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:-/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:-/false} - echo ::set-output name=JOB_DO_TESTING::${JOB_DO_TESTING} - # # * test only binary for arm-type targets - unset CARGO_TEST_OPTIONS - unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-*|aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac; - echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} - echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} - # * strip executable? - STRIP="strip" ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;; aarch64-unknown-linux-musl) STRIP="" ;;esac; - echo set-output name=STRIP::${STRIP} - echo ::set-output name=STRIP::${STRIP} - - name: Create all needed build/work directories - shell: bash - run: | - mkdir -p '${{ steps.vars.outputs.STAGING }}' - mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}' - - name: rust toolchain ~ install - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ steps.vars.outputs.TOOLCHAIN }} - target: ${{ matrix.job.target }} - override: true - profile: minimal # minimal component installation (ie, no documentation) - - name: Info - shell: bash - run: | - gcc --version || true - rustup -V - rustup toolchain list - rustup default - cargo -V - rustc -V - - name: Build - uses: actions-rs/cargo@v1 - with: - use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }} - command: build - args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} - - name: Install cargo-deb - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-deb - if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl' - - name: Build deb - uses: actions-rs/cargo@v1 - with: - command: deb - args: --no-build --target=${{ matrix.job.target }} - if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl' - - name: Test - uses: actions-rs/cargo@v1 - with: - use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }} - command: test - args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} - - name: Archive executable artifacts - uses: actions/upload-artifact@master - with: - name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }} - path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }} - - name: Archive deb artifacts - uses: actions/upload-artifact@master - with: - name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}.deb - path: target/${{ matrix.job.target }}/debian - if: matrix.job.target == 'i686-unknown-linux-musl' || matrix.job.target == 'x86_64-unknown-linux-musl' - - name: Package - shell: bash - run: | - # binary - cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/' - # `strip` binary (if needed) - if [ -n "${{ steps.vars.outputs.STRIP }}" ]; then "${{ steps.vars.outputs.STRIP }}" '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' ; fi - # README and LICENSE - cp README.md '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/' - cp LICENSE '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/' - # base compressed package - pushd '${{ steps.vars.outputs.STAGING }}/' >/dev/null - case ${{ matrix.job.target }} in - *-pc-windows-*) 7z -y a '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* | tail -2 ;; - *) tar czf '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* ;; - esac; - popd >/dev/null - - name: Publish - uses: softprops/action-gh-release@v1 - if: steps.vars.outputs.DEPLOY - with: - files: | - ${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }} - target/${{ matrix.job.target }}/debian/*.deb - - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - ## fix! [rivy; 2020-22-01] `cargo tarpaulin` is unable to test this repo at the moment; alternate recipe or another testing framework? - # coverage: - # name: Code Coverage - # runs-on: ${{ matrix.job.os }} - # strategy: - # fail-fast: true - # matrix: - # # job: [ { os: ubuntu-latest }, { os: macos-latest }, { os: windows-latest } ] - # job: [ { os: ubuntu-latest } ] ## cargo-tarpaulin is currently only available on linux - # steps: - # - uses: actions/checkout@v1 - # # - name: Reattach HEAD ## may be needed for accurate code coverage info - # # run: git checkout ${{ github.head_ref }} - # - name: Initialize workflow variables - # id: vars - # shell: bash - # run: | - # # staging directory - # STAGING='_staging' - # echo set-output name=STAGING::${STAGING} - # echo ::set-output name=STAGING::${STAGING} - # # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see ) - # unset HAS_CODECOV_TOKEN - # if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi - # echo set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN} - # echo ::set-output name=HAS_CODECOV_TOKEN::${HAS_CODECOV_TOKEN} - # env: - # CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" - # - name: Create all needed build/work directories - # shell: bash - # run: | - # mkdir -p '${{ steps.vars.outputs.STAGING }}/work' - # - name: Install required packages - # run: | - # sudo apt-get -y install libssl-dev - # pushd '${{ steps.vars.outputs.STAGING }}/work' >/dev/null - # wget --no-verbose https://github.com/xd009642/tarpaulin/releases/download/0.9.3/cargo-tarpaulin-0.9.3-travis.tar.gz - # tar xf cargo-tarpaulin-0.9.3-travis.tar.gz - # cp cargo-tarpaulin "$(dirname -- "$(which cargo)")"/ - # popd >/dev/null - # - name: Generate coverage - # run: | - # cargo tarpaulin --out Xml - # - name: Upload coverage results (CodeCov.io) - # # CODECOV_TOKEN (aka, "Repository Upload Token" for REPO from CodeCov.io) ## set via REPO/Settings/Secrets - # # if: secrets.CODECOV_TOKEN (not supported {yet?}; see ) - # if: steps.vars.outputs.HAS_CODECOV_TOKEN - # run: | - # # CodeCov.io - # cargo tarpaulin --out Xml - # bash <(curl -s https://codecov.io/bash) - # env: - # CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" diff --git a/.gitignore b/.gitignore index cf19f31..fa39063 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,3 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -# These are backup files generated by rustfmt -**/*.rs.bk -*.swp -.vscode/* -*.idea/* - -#ignore macos files -.DS_Store \ No newline at end of file +.idea +.envrc \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3f09e3a..39e3878 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,17 +40,11 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", "winapi", ] -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "bitflags" version = "1.3.2" @@ -81,135 +75,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clap" -version = "3.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" -dependencies = [ - "atty", - "bitflags", - "clap_lex", - "indexmap", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_complete" -version = "3.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" -dependencies = [ - "clap", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "clap_mangen" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "105180c05a72388d5f5e4e4f6c79eecb92497bda749fa8f963a16647c5d5377f" -dependencies = [ - "clap", - "roff", -] - -[[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 = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "crossbeam-channel" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", -] - [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "doc-comment" version = "0.3.3" @@ -217,23 +88,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] -name = "du-dust" +name = "dust_style_filetree_display" version = "0.8.5" dependencies = [ "ansi_term", "assert_cmd", "atty", - "clap", - "clap_complete", - "clap_mangen", - "config-file", - "directories", "lscolors", - "rayon", "regex", - "serde", "stfu8", - "sysinfo", "tempfile", "terminal_size", "thousands", @@ -277,23 +140,6 @@ dependencies = [ "instant", ] -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hermit-abi" version = "0.1.19" @@ -303,25 +149,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "indexmap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" -dependencies = [ - "autocfg", - "hashbrown", -] - [[package]] name = "instant" version = "0.1.12" @@ -384,24 +211,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - -[[package]] -name = "ntapi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" -dependencies = [ - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -412,28 +221,12 @@ dependencies = [ "winapi", ] -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi 0.2.6", - "libc", -] - [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - [[package]] name = "overload" version = "0.1.1" @@ -467,46 +260,6 @@ dependencies = [ "termtree", ] -[[package]] -name = "proc-macro2" -version = "1.0.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -516,17 +269,6 @@ 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.7.1" @@ -550,12 +292,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "roff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" - [[package]] name = "rustix" version = "0.36.9" @@ -570,31 +306,11 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "serde" version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.156" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "stfu8" @@ -606,38 +322,6 @@ dependencies = [ "regex", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sysinfo" -version = "0.27.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "winapi", -] - [[package]] name = "tempfile" version = "3.4.0" @@ -651,15 +335,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.2.5" @@ -676,53 +351,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - -[[package]] -name = "thiserror" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thousands" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - [[package]] name = "unicode-width" version = "0.1.10" @@ -738,12 +372,6 @@ dependencies = [ "libc", ] -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index a00e8bf..76c668b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,26 +1,19 @@ [package] -name = "du-dust" -description = "A more intuitive version of du" +name = "dust_style_filetree_display" +description = "a rip off of dust's display, useful for applications that want dust's output style for some reason" version = "0.8.5" authors = ["bootandy ", "nebkor "] edition = "2021" readme = "README.md" -documentation = "https://github.com/bootandy/dust" -homepage = "https://github.com/bootandy/dust" -repository = "https://github.com/bootandy/dust" +#documentation = "https://github.com/bootandy/dust" +#homepage = "https://github.com/bootandy/dust" +#repository = "https://github.com/bootandy/dust" +repository = "https://git.emunest.net/rei-forks/dust_style_filetree_display" keywords = ["du", "command-line", "disk", "disk-usage"] -categories = ["command-line-utilities"] license = "Apache-2.0" -[badges] -travis-ci = { repository = "https://travis-ci.org/bootandy/dust" } - -[[bin]] -name = "dust" -path = "src/main.rs" - [profile.release] codegen-units = 1 lto = true @@ -29,18 +22,12 @@ strip = true [dependencies] ansi_term = "0.12" atty = "0.2.14" -clap = "3.2.17" lscolors = "0.13" terminal_size = "0.2" 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" -sysinfo = "0.27" [target.'cfg(windows)'.dependencies] winapi-util = "0.1" @@ -48,41 +35,3 @@ winapi-util = "0.1" [dev-dependencies] assert_cmd = "2" tempfile = "=3" - -[build-dependencies] -clap = "3.2.17" -clap_complete = "3.2.4" -clap_mangen = "0.1" - -[[test]] -name = "integration" -path = "tests/tests.rs" - -[package.metadata.binstall] -pkg-url = "{ repo }/releases/download/v{ version }/dust-v{ version }-{ target }{ archive-suffix }" -bin-dir = "dust-v{ version }-{ target }/{ bin }{ binary-ext }" - -[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'. -""" diff --git a/LICENSE b/LICENCE similarity index 100% rename from LICENSE rename to LICENCE diff --git a/README.md b/README.md index 18c628b..337bce4 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,11 @@ -[![Build Status](https://travis-ci.org/bootandy/dust.svg?branch=master)](https://travis-ci.org/bootandy/dust) +# dust_style_filetree_display -# Dust +This crate contains the display components from [`dust`](https://github.com/bootandy/dust). +They have been ripped off, almost verbatim, from there! +I have removed some of the things that aren't useful for a library user but this is a very quick and rough crude rip-out job — I haven't documented anything or finished taking effort to clean it up. -du + rust = dust. Like du but more intuitive. +The dust style is nice and could be useful in other projects, that want to be look the same as dust +(e.g. because they want to be familiar to dust users, in order to display size tree information +whilst not displaying *exactly* what is on the filesystem). -# Why - -Because I want an easy way to see where my disk is being used. - -# Demo - -![Example](media/snap.png) - -## Install - -#### Cargo Packaging status - -- `cargo install du-dust` - -#### 🍺 Homebrew (Mac OS) - -- `brew install dust` - -#### 🍺 Homebrew (Linux) - -- `brew tap tgotwig/linux-dust && brew install dust` - -#### [Pacstall](https://github.com/pacstall/pacstall) (Debian/Ubuntu) - -- `pacstall -I dust-bin` - -#### [deb-get](https://github.com/wimpysworld/deb-get) (Debian/Ubuntu) - -- `deb-get install du-dust` - -#### Windows: - -- Windows GNU version - works -- Windows MSVC - requires: [VCRUNTIME140.dll](https://docs.microsoft.com/en-gb/cpp/windows/latest-supported-vc-redist?view=msvc-170) - -#### Download - -- Download Linux/Mac binary from [Releases](https://github.com/bootandy/dust/releases) -- unzip file: `tar -xvf _downloaded_file.tar.gz` -- move file to executable path: `sudo mv dust /usr/local/bin/` - -## Overview - -Dust is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust will print a maximum of one 'Did not have permissions message'. - -Dust will list a slightly-less-than-the-terminal-height number of the biggest subdirectories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest subdirectories will be colored. - -The different colors on the bars: These represent the combined tree hierarchy & disk usage. The shades of grey are used to indicate which parent folder a subfolder belongs to. For instance, look at the above screenshot. `.steam` is a folder taking 44% of the space. From the `.steam` bar is a light grey line that goes up. All these folders are inside `.steam` so if you delete `.steam` all that stuff will be gone too. - -## Usage - -``` -Usage: dust -Usage: dust -Usage: dust -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 -D (Show only directories (eg dust -D)) -Usage: dust -F (Show only files - finds your largest files) -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)) -Usage: dust -L (dereference-links - Treat sym links as directories and go into them) -Usage: dust -P (Disable the progress indicator) -Usage: dust -R (For screen readers. Removes bars/symbols. Adds new column: depth level. (May want to use -p for full path too)) -Usage: dust --skip-total (No total row will be displayed) -Usage: dust -z 4000000 (Exclude files below size 4MB) - -``` - -## Alternatives - -- [NCDU](https://dev.yorhel.nl/ncdu) -- [dutree](https://github.com/nachoparker/dutree) -- [dua](https://github.com/Byron/dua-cli/) -- [pdu](https://github.com/KSXGitHub/parallel-disk-usage) -- [dirstat-rs](https://github.com/scullionw/dirstat-rs) -- du -d 1 -h | sort -h - -Note: Apparent-size is calculated slightly differently in dust to gdu. In dust each hard link is counted as using file_length space. In gdu only the first entry is counted. +The `dust` project is licensed under the Apache 2.0 licence, so this crate will do the same. diff --git a/build.rs b/build.rs deleted file mode 100644 index 86e8f7c..0000000 --- a/build.rs +++ /dev/null @@ -1,27 +0,0 @@ -use clap_complete::{generate_to, shells::*}; -use clap_mangen::Man; -use std::fs::File; -use std::io::Error; -use std::path::Path; - -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)?; - - let file = Path::new("man-page").join("dust.1"); - std::fs::create_dir_all("man-page")?; - let mut file = File::create(file)?; - - Man::new(cmd).render(&mut file)?; - - Ok(()) -} diff --git a/ci/before_deploy.ps1 b/ci/before_deploy.ps1 deleted file mode 100644 index 710a503..0000000 --- a/ci/before_deploy.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -# This script takes care of packaging the build artifacts that will go in the -# release zipfile - -$SRC_DIR = $PWD.Path -$STAGE = [System.Guid]::NewGuid().ToString() - -Set-Location $ENV:Temp -New-Item -Type Directory -Name $STAGE -Set-Location $STAGE - -$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip" - -# TODO Update this to package the right artifacts -Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\dust" '.\' - -7z a "$ZIP" * - -Push-AppveyorArtifact "$ZIP" - -Remove-Item *.* -Force -Set-Location .. -Remove-Item $STAGE -Set-Location $SRC_DIR diff --git a/ci/before_deploy.sh b/ci/before_deploy.sh deleted file mode 100644 index ab4426d..0000000 --- a/ci/before_deploy.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -# This script takes care of building your crate and packaging it for release - -set -ex - -main() { - local src=$(pwd) \ - stage= - - case $TRAVIS_OS_NAME in - linux) - stage=$(mktemp -d) - ;; - osx) - stage=$(mktemp -d -t tmp) - ;; - esac - - test -f Cargo.lock || cargo generate-lockfile - - # TODO Update this to build the artifacts that matter to you - cross rustc --bin dust --target $TARGET --release -- -C lto - - # TODO Update this to package the right artifacts - cp target/$TARGET/release/dust $stage/ - - cd $stage - tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz * - cd $src - - rm -rf $stage -} - -main diff --git a/ci/how2publish.txt b/ci/how2publish.txt deleted file mode 100644 index 57addb2..0000000 --- a/ci/how2publish.txt +++ /dev/null @@ -1,14 +0,0 @@ -# ----------- To do a release --------- -# Compare times of runs to check no drastic slow down: -# time target/release/dust ~/dev -# time dust ~dev - -# 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 . diff --git a/ci/install.sh b/ci/install.sh deleted file mode 100644 index 317a401..0000000 --- a/ci/install.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -set -ex - -main() { - local target= - if [ $TRAVIS_OS_NAME = linux ]; then - target=x86_64-unknown-linux-musl - sort=sort - else - target=x86_64-apple-darwin - sort=gsort # for `sort --sort-version`, from brew's coreutils. - fi - - # This fetches latest stable release - local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \ - | cut -d/ -f3 \ - | grep -E '^v[0.1.0-9.]+$' \ - | $sort --version-sort \ - | tail -n1) - curl -LSfs https://japaric.github.io/trust/install.sh | \ - sh -s -- \ - --force \ - --git japaric/cross \ - --tag $tag \ - --target $target -} - -main diff --git a/ci/script.sh b/ci/script.sh deleted file mode 100644 index 686ec6d..0000000 --- a/ci/script.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# This script takes care of testing your crate - -set -ex - -# TODO This is the "test phase", tweak it as you see fit -main() { - cross build --target $TARGET - cross build --target $TARGET --release - - if [ ! -z $DISABLE_TESTS ]; then - return - fi - - cross test --target $TARGET - cross test --target $TARGET --release - - cross run --target $TARGET - cross run --target $TARGET --release -} - -# we don't run the "test phase" when doing deploys -if [ -z $TRAVIS_TAG ]; then - main -fi diff --git a/completions/_dust b/completions/_dust deleted file mode 100644 index 9e8be82..0000000 --- a/completions/_dust +++ /dev/null @@ -1,77 +0,0 @@ -#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]' \ -'-L[dereference sym links - Treat sym links as directories and go into them]' \ -'--dereference-links[dereference sym links - Treat sym links as directories and go into them]' \ -'-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]' \ -'-R[For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)]' \ -'--screen-reader[For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)]' \ -'--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 -D --only-dir)-t[show only these file types]' \ -'(-d --depth -D --only-dir)--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)]' \ -'-P[Disable the progress indication.]' \ -'--no-progress[Disable the progress indication.]' \ -'(-F --only-file -t --file_types)-D[Only directories will be displayed.]' \ -'(-F --only-file -t --file_types)--only-dir[Only directories will be displayed.]' \ -'(-D --only-dir)-F[Only files will be displayed. (Finds your largest files)]' \ -'(-D --only-dir)--only-file[Only files will be displayed. (Finds your largest files)]' \ -'*::inputs:' \ -&& ret=0 -} - -(( $+functions[_dust_commands] )) || -_dust_commands() { - local commands; commands=() - _describe -t commands 'dust commands' commands "$@" -} - -_dust "$@" diff --git a/completions/_dust.ps1 b/completions/_dust.ps1 deleted file mode 100644 index f87a5b9..0000000 --- a/completions/_dust.ps1 +++ /dev/null @@ -1,79 +0,0 @@ - -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('-L', 'L', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them') - [CompletionResult]::new('--dereference-links', 'dereference-links', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them') - [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('-R', 'R', [CompletionResultType]::ParameterName, 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)') - [CompletionResult]::new('--screen-reader', 'screen-reader', [CompletionResultType]::ParameterName, 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)') - [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)') - [CompletionResult]::new('-P', 'P', [CompletionResultType]::ParameterName, 'Disable the progress indication.') - [CompletionResult]::new('--no-progress', 'no-progress', [CompletionResultType]::ParameterName, 'Disable the progress indication.') - [CompletionResult]::new('-D', 'D', [CompletionResultType]::ParameterName, 'Only directories will be displayed.') - [CompletionResult]::new('--only-dir', 'only-dir', [CompletionResultType]::ParameterName, 'Only directories will be displayed.') - [CompletionResult]::new('-F', 'F', [CompletionResultType]::ParameterName, 'Only files will be displayed. (Finds your largest files)') - [CompletionResult]::new('--only-file', 'only-file', [CompletionResultType]::ParameterName, 'Only files will be displayed. (Finds your largest files)') - break - } - }) - - $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | - Sort-Object -Property ListItemText -} diff --git a/completions/dust.bash b/completions/dust.bash deleted file mode 100644 index 83d7b65..0000000 --- a/completions/dust.bash +++ /dev/null @@ -1,94 +0,0 @@ -_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 -L -x -s -r -c -b -z -R -f -i -v -e -t -w -H -P -D -F --help --version --depth --number-of-lines --full-paths --ignore-directory --dereference-links --limit-filesystem --apparent-size --reverse --no-colors --no-percent-bars --min-size --screen-reader --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --si --no-progress --only-dir --only-file ..." - 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 diff --git a/completions/dust.elv b/completions/dust.elv deleted file mode 100644 index a11c4a9..0000000 --- a/completions/dust.elv +++ /dev/null @@ -1,73 +0,0 @@ - -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 -L 'dereference sym links - Treat sym links as directories and go into them' - cand --dereference-links 'dereference sym links - Treat sym links as directories and go into them' - 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 -R 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)' - cand --screen-reader 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)' - 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)' - cand -P 'Disable the progress indication.' - cand --no-progress 'Disable the progress indication.' - cand -D 'Only directories will be displayed.' - cand --only-dir 'Only directories will be displayed.' - cand -F 'Only files will be displayed. (Finds your largest files)' - cand --only-file 'Only files will be displayed. (Finds your largest files)' - } - ] - $completions[$command] -} diff --git a/completions/dust.fish b/completions/dust.fish deleted file mode 100644 index 526530f..0000000 --- a/completions/dust.fish +++ /dev/null @@ -1,25 +0,0 @@ -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 L -l dereference-links -d 'dereference sym links - Treat sym links as directories and go into them' -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 -s R -l screen-reader -d 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)' -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)' -complete -c dust -s P -l no-progress -d 'Disable the progress indication.' -complete -c dust -s D -l only-dir -d 'Only directories will be displayed.' -complete -c dust -s F -l only-file -d 'Only files will be displayed. (Finds your largest files)' diff --git a/config/config.toml b/config/config.toml deleted file mode 100644 index 314cd2a..0000000 --- a/config/config.toml +++ /dev/null @@ -1,28 +0,0 @@ -# Sample Config file, works with toml and yaml -# Place in either: -# ~/.config/dust/config.toml -# ~/.dust.toml - -# Print tree upside down (biggest highest) -reverse=true - -# Subdirectories will not have their path shortened -display-full-paths=true - -# Use file length instead of blocks -display-apparent-size=true - -# No colors will be printed -no-colors=true - -# No percent bars or percentages will be displayed -no-bars=true - -# No total row will be displayed -skip-total=true - -# Do not display hidden files -ignore-hidden=true - -# print sizes in powers of 1000 (e.g., 1.1G) -iso=true diff --git a/man-page/dust.1 b/man-page/dust.1 deleted file mode 100644 index e05bfb1..0000000 --- a/man-page/dust.1 +++ /dev/null @@ -1,90 +0,0 @@ -.ie \n(.g .ds Aq \(aq -.el .ds Aq ' -.TH Dust 1 "Dust 0.8.5" -.SH NAME -Dust \- Like du but more intuitive -.SH SYNOPSIS -\fBDust\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fB\-d\fR|\fB\-\-depth\fR] [\fB\-n\fR|\fB\-\-number\-of\-lines\fR] [\fB\-p\fR|\fB\-\-full\-paths\fR] [\fB\-X\fR|\fB\-\-ignore\-directory\fR] [\fB\-L\fR|\fB\-\-dereference\-links\fR] [\fB\-x\fR|\fB\-\-limit\-filesystem\fR] [\fB\-s\fR|\fB\-\-apparent\-size\fR] [\fB\-r\fR|\fB\-\-reverse\fR] [\fB\-c\fR|\fB\-\-no\-colors\fR] [\fB\-b\fR|\fB\-\-no\-percent\-bars\fR] [\fB\-z\fR|\fB\-\-min\-size\fR] [\fB\-R\fR|\fB\-\-screen\-reader\fR] [\fB\-\-skip\-total\fR] [\fB\-f\fR|\fB\-\-filecount\fR] [\fB\-i\fR|\fB\-\-ignore_hidden\fR] [\fB\-v\fR|\fB\-\-invert\-filter\fR] [\fB\-e\fR|\fB\-\-filter\fR] [\fB\-t\fR|\fB\-\-file_types\fR] [\fB\-w\fR|\fB\-\-terminal_width\fR] [\fB\-H\fR|\fB\-\-si\fR] [\fB\-P\fR|\fB\-\-no\-progress\fR] [\fB\-D\fR|\fB\-\-only\-dir\fR] [\fB\-F\fR|\fB\-\-only\-file\fR] [\fIinputs\fR] -.SH DESCRIPTION -Like du but more intuitive -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -Print help information -.TP -\fB\-V\fR, \fB\-\-version\fR -Print version information -.TP -\fB\-d\fR, \fB\-\-depth\fR -Depth to show -.TP -\fB\-n\fR, \fB\-\-number\-of\-lines\fR -Number of lines of output to show. (Default is terminal_height \- 10) -.TP -\fB\-p\fR, \fB\-\-full\-paths\fR -Subdirectories will not have their path shortened -.TP -\fB\-X\fR, \fB\-\-ignore\-directory\fR -Exclude any file or directory with this name -.TP -\fB\-L\fR, \fB\-\-dereference\-links\fR -dereference sym links \- Treat sym links as directories and go into them -.TP -\fB\-x\fR, \fB\-\-limit\-filesystem\fR -Only count the files and directories on the same filesystem as the supplied directory -.TP -\fB\-s\fR, \fB\-\-apparent\-size\fR -Use file length instead of blocks -.TP -\fB\-r\fR, \fB\-\-reverse\fR -Print tree upside down (biggest highest) -.TP -\fB\-c\fR, \fB\-\-no\-colors\fR -No colors will be printed (Useful for commands like: watch) -.TP -\fB\-b\fR, \fB\-\-no\-percent\-bars\fR -No percent bars or percentages will be displayed -.TP -\fB\-z\fR, \fB\-\-min\-size\fR -Minimum size file to include in output -.TP -\fB\-R\fR, \fB\-\-screen\-reader\fR -For screen readers. Removes bars. Adds new column: depth level (May want to use \-p too for full path) -.TP -\fB\-\-skip\-total\fR -No total row will be displayed -.TP -\fB\-f\fR, \fB\-\-filecount\fR -Directory \*(Aqsize\*(Aq is number of child files/dirs not disk size -.TP -\fB\-i\fR, \fB\-\-ignore_hidden\fR -Do not display hidden files -.TP -\fB\-v\fR, \fB\-\-invert\-filter\fR -Exclude filepaths matching this regex. To ignore png files type: \-v "\\.png$" -.TP -\fB\-e\fR, \fB\-\-filter\fR -Only include filepaths matching this regex. For png files type: \-e "\\.png$" -.TP -\fB\-t\fR, \fB\-\-file_types\fR -show only these file types -.TP -\fB\-w\fR, \fB\-\-terminal_width\fR -Specify width of output overriding the auto detection of terminal width -.TP -\fB\-H\fR, \fB\-\-si\fR -print sizes in powers of 1000 (e.g., 1.1G) -.TP -\fB\-P\fR, \fB\-\-no\-progress\fR -Disable the progress indication. -.TP -\fB\-D\fR, \fB\-\-only\-dir\fR -Only directories will be displayed. -.TP -\fB\-F\fR, \fB\-\-only\-file\fR -Only files will be displayed. (Finds your largest files) -.TP -[\fIinputs\fR] - -.SH VERSION -v0.8.5 diff --git a/media/snap.png b/media/snap.png deleted file mode 100644 index 0ee57a3d18d41efbbbf4931ea6f0402531ebd590..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62704 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
  • 5@#hR#bB=Dw^LCqO|}S0)byl! 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

    5-Q42}UX&birej|7jM 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 diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..4261562 --- /dev/null +++ b/shell.nix @@ -0,0 +1,19 @@ +{ pkgs ? import {} }: + +let + # We may need some packages from nixpkgs-unstable + #unstable = import {}; + + rust-toolchain = pkgs.symlinkJoin { + name = "rust-toolchain"; + paths = [pkgs.rustc pkgs.cargo pkgs.clippy pkgs.rustfmt pkgs.rustPlatform.rustcSrc]; + }; +in + +pkgs.mkShell { + + buildInputs = [ + rust-toolchain + ]; + +} diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 4d8ffd8..0000000 --- a/src/cli.rs +++ /dev/null @@ -1,172 +0,0 @@ -use clap::{Arg, Command}; - -// For single thread mode set this variable on your command line: -// export RAYON_NUM_THREADS=1 - -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("dereference_links") - .short('L') - .long("dereference-links") - .help("dereference sym links - Treat sym links as directories and go into them"), - ) - .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("screen_reader") - .short('R') - .long("screen-reader") - .help("For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)"), - ) - .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") - .conflicts_with("only_dir") - .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("disable_progress") - .short('P') - .long("no-progress") - .help("Disable the progress indication."), - ) - .arg( - Arg::new("only_dir") - .short('D') - .long("only-dir") - .conflicts_with("only_file") - .conflicts_with("types") - .help("Only directories will be displayed."), - ) - .arg( - Arg::new("only_file") - .short('F') - .long("only-file") - .conflicts_with("only_dir") - .help("Only files will be displayed. (Finds your largest files)"), - ) - .arg(Arg::new("inputs").multiple_occurrences(true)) -} diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 9a77f52..0000000 --- a/src/config.rs +++ /dev/null @@ -1,215 +0,0 @@ -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, - pub display_apparent_size: Option, - pub reverse: Option, - pub no_colors: Option, - pub no_bars: Option, - pub skip_total: Option, - pub screen_reader: Option, - pub ignore_hidden: Option, - pub iso: Option, - pub min_size: Option, - pub only_dir: Option, - pub only_file: Option, - pub disable_progress: Option, - pub depth: Option, -} - -impl Config { - pub fn get_no_colors(&self, options: &ArgMatches) -> bool { - Some(true) == self.no_colors || options.is_present("no_colors") - } - pub fn get_disable_progress(&self, options: &ArgMatches) -> bool { - Some(true) == self.disable_progress || options.is_present("disable_progress") - } - 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 { - // If we are only showing files, always show full paths - Some(true) == self.display_full_paths - || options.is_present("display_full_paths") - || self.get_only_file(options) - } - 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_screen_reader(&self, options: &ArgMatches) -> bool { - Some(true) == self.screen_reader || options.is_present("screen_reader") - } - pub fn get_depth(&self, options: &ArgMatches) -> usize { - if let Some(v) = options.value_of("depth") { - if let Ok(v) = v.parse::() { - return v; - } - } - - self.depth.unwrap_or(usize::MAX) - } - pub fn get_min_size(&self, options: &ArgMatches, iso: bool) -> Option { - 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 { - 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 - } - } - pub fn get_only_dir(&self, options: &ArgMatches) -> bool { - Some(true) == self.only_dir || options.is_present("only_dir") - } - pub fn get_only_file(&self, options: &ArgMatches) -> bool { - Some(true) == self.only_file || options.is_present("only_file") - } -} - -fn convert_min_size(input: &str, iso: bool) -> Option { - let chars_as_vec: Vec = input.chars().collect(); - match chars_as_vec.split_last() { - Some((last, start)) => { - let mut starts: String = start.iter().collect::(); - - for (i, u) in UNITS.iter().rev().enumerate() { - if Some(*u) == last.to_uppercase().next() { - return match starts.parse::() { - 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 { - 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() - } -} - -#[cfg(test)] -mod tests { - #[allow(unused_imports)] - use super::*; - use clap::{Arg, ArgMatches, Command}; - - #[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)); - } - - #[test] - fn test_get_depth() { - // No config and no flag. - let c = Config::default(); - let args = get_args(vec![]); - assert_eq!(c.get_depth(&args), usize::MAX); - - // Config is not defined and flag is defined. - let c = Config::default(); - let args = get_args(vec!["dust", "--depth", "5"]); - assert_eq!(c.get_depth(&args), 5); - - // Config is defined and flag is not defined. - let c = Config { - depth: Some(3), - ..Default::default() - }; - let args = get_args(vec![]); - assert_eq!(c.get_depth(&args), 3); - - // Both config and flag are defined. - let c = Config { - depth: Some(3), - ..Default::default() - }; - let args = get_args(vec!["dust", "--depth", "5"]); - assert_eq!(c.get_depth(&args), 5); - } - - fn get_args(args: Vec<&str>) -> ArgMatches { - Command::new("Dust") - .trailing_var_arg(true) - .arg(Arg::new("depth").long("depth").takes_value(true)) - .get_matches_from(args) - } -} diff --git a/src/dir_walker.rs b/src/dir_walker.rs deleted file mode 100644 index 2863751..0000000 --- a/src/dir_walker.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::fs; -use std::sync::Arc; - -use crate::node::Node; -use crate::progress::Operation; -use crate::progress::PAtomicInfo; -use crate::progress::ORDERING; -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::collections::HashSet; - -use crate::node::build_node; -use std::fs::DirEntry; - -use crate::platform::get_metadata; -pub struct WalkData<'a> { - pub ignore_directories: HashSet, - pub filter_regex: &'a [Regex], - pub invert_filter_regex: &'a [Regex], - pub allowed_filesystems: HashSet, - pub use_apparent_size: bool, - pub by_filecount: bool, - pub ignore_hidden: bool, - pub follow_links: bool, - pub progress_data: Arc, -} - -pub fn walk_it(dirs: HashSet, walk_data: WalkData) -> Vec { - let mut inodes = HashSet::new(); - let top_level_nodes: Vec<_> = dirs - .into_iter() - .filter_map(|d| { - let prog_data = &walk_data.progress_data; - prog_data.clear_state(&d); - let node = walk(d, &walk_data, 0)?; - - prog_data.state.store(Operation::PREPARING, ORDERING); - - clean_inodes(node, &mut inodes, walk_data.use_apparent_size) - }) - .collect(); - top_level_nodes -} - -// 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 { - 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::(), - 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, walk_data: &WalkData, depth: usize) -> Option { - let prog_data = &walk_data.progress_data; - 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(), walk_data, depth) - - if !ignore_file(entry, walk_data) { - if let Ok(data) = entry.file_type() { - if data.is_dir() || (walk_data.follow_links && data.is_symlink()) { - return walk(entry.path(), walk_data, depth + 1); - } - - let node = 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, - ); - - prog_data.num_files.fetch_add(1, ORDERING); - if let Some(ref file) = node { - prog_data.total_file_size.fetch_add(file.size, ORDERING); - } - - return node; - } - } - } else { - prog_data.no_permissions.store(true, ORDERING) - } - None - }) - .collect(); - } else if !dir.is_file() { - walk_data.progress_data.no_permissions.store(true, ORDERING) - } - 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())); - } -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b76da6f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,178 @@ +pub mod display; +pub mod display_node; +pub mod filter; +pub mod filter_type; +pub mod node; +pub mod utils; + +use std::cmp::max; + +use terminal_size::{terminal_size, Height, Width}; + +static DEFAULT_NUMBER_OF_LINES: usize = 30; +static DEFAULT_TERMINAL_WIDTH: usize = 80; + +pub 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 + } +} + +pub 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 +} + +pub 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 main() { +// let options = build_cli().get_matches(); +// let stdin_lines = get_lines_from_stdin(); +// +// let target_dirs = match options.values_of("inputs") { +// Some(values) => values.collect(), +// None => stdin_lines.as_ref().map_or(vec!["."], |lines| { +// lines.iter().map(String::as_str).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 = config.get_depth(&options); +// +// // 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 = 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 no_colors = init_color(config.get_no_colors(&options)); +// +// let ignore_directories = options +// .values_of("ignore_directory") +// .unwrap_or_default() +// .map(PathBuf::from); +// +// let by_filecount = options.is_present("by_filecount"); +// let limit_filesystem = options.is_present("limit_filesystem"); +// let follow_links = options.is_present("dereference_links"); +// +// let simplified_dirs = simplify_dir_names(target_dirs); +// let allowed_filesystems = limit_filesystem +// .then(|| get_filesystem_devices(simplified_dirs.iter())) +// .unwrap_or_default(); +// +// let ignored_full_path: HashSet = ignore_directories +// .flat_map(|x| simplified_dirs.iter().map(move |d| d.join(&x))) +// .collect(); +// +// // let iso = config.get_iso(&options); +// +// // let ignore_hidden = config.get_ignore_hidden(&options); +// +// let mut indicator = PIndicator::build_me(); +// // if !config.get_disable_progress(&options) { +// // indicator.spawn(iso); +// // } +// +// let result = panic::catch_unwind(|| init_rayon); +// if result.is_err() { +// eprintln!("Problem initializing rayon, try: export RAYON_NUM_THREADS=1") +// } +// +// let top_level_nodes = walk_it(simplified_dirs, walk_data); +// +// let tree = match summarize_file_types { +// true => get_all_file_types(&top_level_nodes, number_of_lines), +// false => { +// let agg_data = AggregateData { +// min_size: config.get_min_size(&options, iso), +// only_dir: config.get_only_dir(&options), +// only_file: config.get_only_file(&options), +// number_of_lines, +// depth, +// using_a_filter: options.values_of("filter").is_some() +// || options.value_of("invert_filter").is_some(), +// }; +// get_biggest(top_level_nodes, agg_data) +// } +// }; +// +// let failed_permissions = indicator.data.no_permissions.load(ORDERING); +// indicator.stop(); +// // Must have stopped indicator before we print to stderr +// if failed_permissions { +// eprintln!("Did not have permissions for all directories"); +// } +// +// if let Some(root_node) = tree { +// let idd = InitialDisplayData { +// short_paths: !config.get_full_paths(&options), +// is_reversed: !config.get_reverse(&options), +// colors_on: !no_colors, +// by_filecount, +// iso, +// is_screen_reader: config.get_screen_reader(&options), +// }; +// draw_it( +// idd, +// config.get_no_bars(&options), +// terminal_width, +// &root_node, +// config.get_skip_total(&options), +// ) +// } +// } diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index fb1420b..0000000 --- a/src/main.rs +++ /dev/null @@ -1,257 +0,0 @@ -mod cli; -mod config; -mod dir_walker; -mod display; -mod display_node; -mod filter; -mod filter_type; -mod node; -mod platform; -mod progress; -mod utils; - -use crate::cli::build_cli; -use dir_walker::WalkData; -use display::InitialDisplayData; -use filter::AggregateData; -use progress::PIndicator; -use progress::ORDERING; -use std::collections::HashSet; -use std::io::BufRead; -use std::panic; -use std::process; -use sysinfo::{System, SystemExt}; - -use self::display::draw_it; -use clap::Values; -use config::get_config; -use dir_walker::walk_it; -use filter::get_biggest; -use filter_type::get_all_file_types; -use rayon::ThreadPoolBuildError; -use regex::Regex; -use std::cmp::max; -use std::path::PathBuf; -use terminal_size::{terminal_size, Height, Width}; -use utils::get_filesystem_devices; -use utils::simplify_dir_names; - -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 - } -} - -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) -> Vec { - 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() -} - -// Returns a list of lines from stdin or `None` if there's nothing to read -fn get_lines_from_stdin() -> Option> { - atty::isnt(atty::Stream::Stdin).then(|| { - std::io::stdin() - .lock() - .lines() - .collect::>() - .expect("Error reading from stdin") - }) -} - -fn main() { - let options = build_cli().get_matches(); - let config = get_config(); - let stdin_lines = get_lines_from_stdin(); - - let target_dirs = match options.values_of("inputs") { - Some(values) => values.collect(), - None => stdin_lines.as_ref().map_or(vec!["."], |lines| { - lines.iter().map(String::as_str).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 = config.get_depth(&options); - - // 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 = 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 no_colors = init_color(config.get_no_colors(&options)); - - let ignore_directories = options - .values_of("ignore_directory") - .unwrap_or_default() - .map(PathBuf::from); - - let by_filecount = options.is_present("by_filecount"); - let limit_filesystem = options.is_present("limit_filesystem"); - let follow_links = options.is_present("dereference_links"); - - let simplified_dirs = simplify_dir_names(target_dirs); - let allowed_filesystems = limit_filesystem - .then(|| get_filesystem_devices(simplified_dirs.iter())) - .unwrap_or_default(); - - let ignored_full_path: HashSet = ignore_directories - .flat_map(|x| simplified_dirs.iter().map(move |d| d.join(&x))) - .collect(); - - let iso = config.get_iso(&options); - - let ignore_hidden = config.get_ignore_hidden(&options); - - let mut indicator = PIndicator::build_me(); - if !config.get_disable_progress(&options) { - indicator.spawn(iso); - } - - 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, - follow_links, - progress_data: indicator.data.clone(), - }; - - let result = panic::catch_unwind(|| init_rayon); - if result.is_err() { - eprintln!("Problem initializing rayon, try: export RAYON_NUM_THREADS=1") - } - - let top_level_nodes = walk_it(simplified_dirs, walk_data); - - let tree = match summarize_file_types { - true => get_all_file_types(&top_level_nodes, number_of_lines), - false => { - let agg_data = AggregateData { - min_size: config.get_min_size(&options, iso), - only_dir: config.get_only_dir(&options), - only_file: config.get_only_file(&options), - number_of_lines, - depth, - using_a_filter: options.values_of("filter").is_some() - || options.value_of("invert_filter").is_some(), - }; - get_biggest(top_level_nodes, agg_data) - } - }; - - let failed_permissions = indicator.data.no_permissions.load(ORDERING); - indicator.stop(); - // Must have stopped indicator before we print to stderr - if failed_permissions { - eprintln!("Did not have permissions for all directories"); - } - - if let Some(root_node) = tree { - let idd = InitialDisplayData { - short_paths: !config.get_full_paths(&options), - is_reversed: !config.get_reverse(&options), - colors_on: !no_colors, - by_filecount, - iso, - is_screen_reader: config.get_screen_reader(&options), - }; - draw_it( - idd, - config.get_no_bars(&options), - terminal_width, - &root_node, - config.get_skip_total(&options), - ) - } -} - -fn init_rayon() -> Result<(), ThreadPoolBuildError> { - let large_stack = usize::pow(1024, 3); - let mut s = System::new(); - s.refresh_memory(); - let available = s.available_memory(); - - if available > large_stack.try_into().unwrap() { - // Larger stack size to handle cases with lots of nested directories - rayon::ThreadPoolBuilder::new() - .stack_size(large_stack) - .build_global() - } else { - rayon::ThreadPoolBuilder::new().build_global() - } -} diff --git a/src/node.rs b/src/node.rs index 78ecbf0..f799cde 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,8 +1,3 @@ -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; @@ -15,47 +10,6 @@ pub struct Node { pub depth: usize, } -#[allow(clippy::too_many_arguments)] -pub fn build_node( - dir: PathBuf, - children: Vec, - filter_regex: &[Regex], - invert_filter_regex: &[Regex], - use_apparent_size: bool, - is_symlink: bool, - is_file: bool, - by_filecount: bool, - depth: usize, -) -> Option { - 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 diff --git a/src/platform.rs b/src/platform.rs deleted file mode 100644 index 77c578a..0000000 --- a/src/platform.rs +++ /dev/null @@ -1,128 +0,0 @@ -#[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>(path: P) -> io::Result { - 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), - } -} diff --git a/src/utils.rs b/src/utils.rs index 30ea705..e688dad 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,6 @@ -use platform::get_metadata; use std::collections::HashSet; use std::path::{Path, PathBuf}; -use crate::platform; use regex::Regex; pub fn simplify_dir_names>(filenames: Vec

    ) -> HashSet { @@ -31,17 +29,6 @@ pub fn simplify_dir_names>(filenames: Vec

    ) -> HashSet top_level_names } -pub fn get_filesystem_devices<'a, P: IntoIterator>(paths: P) -> HashSet { - // 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>(path: P) -> PathBuf { // normalize path ... // 1. removing repeated separators diff --git a/tests/test_dir/many/a_file b/tests/test_dir/many/a_file deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_dir/many/hello_file b/tests/test_dir/many/hello_file deleted file mode 100644 index ce01362..0000000 --- a/tests/test_dir/many/hello_file +++ /dev/null @@ -1 +0,0 @@ -hello diff --git a/tests/test_dir2/dir/hello b/tests/test_dir2/dir/hello deleted file mode 100644 index b6fc4c6..0000000 --- a/tests/test_dir2/dir/hello +++ /dev/null @@ -1 +0,0 @@ -hello \ No newline at end of file diff --git a/tests/test_dir2/dir_name_clash b/tests/test_dir2/dir_name_clash deleted file mode 100644 index b6fc4c6..0000000 --- a/tests/test_dir2/dir_name_clash +++ /dev/null @@ -1 +0,0 @@ -hello \ No newline at end of file diff --git a/tests/test_dir2/dir_substring/hello b/tests/test_dir2/dir_substring/hello deleted file mode 100644 index ce01362..0000000 --- a/tests/test_dir2/dir_substring/hello +++ /dev/null @@ -1 +0,0 @@ -hello diff --git a/tests/test_dir2/long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes_over_80_characters_i_wonder b/tests/test_dir2/long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes_over_80_characters_i_wonder deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_dir_hidden_entries/.hidden_file b/tests/test_dir_hidden_entries/.hidden_file deleted file mode 100644 index 32f95c0..0000000 --- a/tests/test_dir_hidden_entries/.hidden_file +++ /dev/null @@ -1 +0,0 @@ -hi \ No newline at end of file diff --git a/tests/test_dir_unicode/ラウトは難しいです!.japan b/tests/test_dir_unicode/ラウトは難しいです!.japan deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_dir_unicode/👩.unicode b/tests/test_dir_unicode/👩.unicode deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_exact_output.rs b/tests/test_exact_output.rs deleted file mode 100644 index 4302c0b..0000000 --- a/tests/test_exact_output.rs +++ /dev/null @@ -1,208 +0,0 @@ -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::(); - 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>(valid_outputs: Vec, command_args: Vec) { - 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 { - // 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 { - 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 { - 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 { - // 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 { - // 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] -} diff --git a/tests/test_flags.rs b/tests/test_flags.rs deleted file mode 100644 index 583f87c..0000000 --- a/tests/test_flags.rs +++ /dev/null @@ -1,222 +0,0 @@ -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>(command_args: Vec) -> 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")); -} -// Add test for multiple dirs - with -d 0 and maybe -d 1 check the - -#[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] -#[cfg(target_family = "unix")] -pub fn test_show_files_only() { - let output = build_command(vec!["-c", "-F", "tests/test_dir"]); - assert!(output.contains("tests/test_dir/many/a_file")); - assert!(output.contains("tests/test_dir/many/hello_file")); - assert!(!output.contains("tests/test_dir/many ")); -} - -#[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_output_screen_reader() { - let output = build_command(vec!["--screen-reader", "-c", "tests/test_dir/"]); - println!("{}", output); - assert!(output.contains("test_dir 0")); - assert!(output.contains("many 1")); - assert!(output.contains("hello_file 2")); - assert!(output.contains("a_file 2")); - - // Verify no 'symbols' reported by screen reader - assert!(!output.contains('│')); - - for block in ['█', '▓', '▒', '░'] { - assert!(!output.contains(block)); - } -} - -#[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")); -} diff --git a/tests/tests.rs b/tests/tests.rs deleted file mode 100644 index 8b13789..0000000 --- a/tests/tests.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/tests_symlinks.rs b/tests/tests_symlinks.rs deleted file mode 100644 index 55f1c3f..0000000 --- a/tests/tests_symlinks.rs +++ /dev/null @@ -1,141 +0,0 @@ -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 -} - -fn link_it(link_path: PathBuf, file_path_s: &str, is_soft: bool) -> String { - let link_name_s = link_path.to_str().unwrap(); - let mut c = Command::new("ln"); - if is_soft { - c.arg("-s"); - } - c.arg(file_path_s); - c.arg(link_name_s); - assert!(c.output().is_ok()); - link_name_s.into() -} - -#[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_it(link_name, file_path_s, true); - - 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"); - link_it(link_name, file_path_s, false); - - 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_hard_sym_link_no_dup_multi_arg() { - let dir = Builder::new().tempdir().unwrap(); - let dir_link = Builder::new().tempdir().unwrap(); - let file = build_temp_file(&dir); - let dir_s = dir.path().to_str().unwrap(); - let dir_link_s = dir_link.path().to_str().unwrap(); - let file_path_s = file.to_str().unwrap(); - - let link_name = dir_link.path().join("the_link"); - let link_name_s = link_it(link_name, file_path_s, false); - - 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", "-b", dir_link_s, dir_s]) - .unwrap() - .stdout; - - // The link or the file should appear but not both - let output = str::from_utf8(&output).unwrap(); - let has_file_only = output.contains(file_path_s) && !output.contains(&link_name_s); - let has_link_only = !output.contains(file_path_s) && output.contains(&link_name_s); - assert!(has_file_only || has_link_only); -} - -#[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_it(link_name, dir_s, true); - - 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") - .arg("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())); -}