Compare commits

..

1 Commits

Author SHA1 Message Date
andy.boot
ba04d4c42c Increment version 2018-04-27 12:44:24 +01:00
35 changed files with 1087 additions and 2526 deletions

View File

@@ -1,297 +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:-<empty>/false}
echo ::set-output name=JOB_DO_FORMAT_TESTING::${JOB_DO_FORMAT_TESTING}
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt, clippy
- name: "`fmt` testing"
if: steps.vars.outputs.JOB_DO_FORMAT_TESTING
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: "`clippy` testing"
if: success() || failure() # run regardless of prior step ("`fmt` testing") success/failure
uses: actions-rs/cargo@v1
with:
command: clippy
args: ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings
min_version:
name: MinSRV # Minimum supported rust version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_MIN_SRV }}
profile: minimal # minimal component installation (ie, no documentation)
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
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: arm-unknown-linux-gnueabihf , use-cross: use-cross }
- { os: ubuntu-20.04 , target: i686-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-20.04 , target: i686-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-20.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
- { os: ubuntu-20.04 , target: x86_64-unknown-linux-musl , use-cross: use-cross }
- { os: ubuntu-18.04 , target: x86_64-unknown-linux-gnu , use-cross: use-cross }
- { os: macos-latest , target: x86_64-apple-darwin }
- { os: windows-latest , target: i686-pc-windows-gnu }
- { os: windows-latest , target: i686-pc-windows-msvc }
- { os: windows-latest , target: x86_64-pc-windows-gnu } ## !maint: [rivy; 2020-01-21] may break due to rust bug; follow possible solution from GH:rust-lang/rust#47048 (refs: GH:rust-lang/rust#47048 , GH:rust-lang/rust#53454 , GH:bike-barn/hermit#172 )
- { os: windows-latest , target: x86_64-pc-windows-msvc }
steps:
- uses: actions/checkout@v1
- name: Install any prerequisites
shell: bash
run: |
case ${{ matrix.job.target }} in
arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
esac
- name: Initialize workflow variables
id: vars
shell: bash
run: |
# toolchain
TOOLCHAIN="stable" ## default to "stable" toolchain
# * specify alternate TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: <https://github.com/rust-lang/rust/issues/47048>, <https://github.com/rust-lang/rust/issues/53454>, <https://github.com/rust-lang/cargo/issues/6754>)
case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
# * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
echo set-output name=TOOLCHAIN::${TOOLCHAIN}
echo ::set-output name=TOOLCHAIN::${TOOLCHAIN}
# staging directory
STAGING='_staging'
echo set-output name=STAGING::${STAGING}
echo ::set-output name=STAGING::${STAGING}
# determine EXE suffix
EXE_suffix="" ; case ${{ matrix.job.target }} in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
echo set-output name=EXE_suffix::${EXE_suffix}
echo ::set-output name=EXE_suffix::${EXE_suffix}
# parse commit reference info
REF_NAME=${GITHUB_REF#refs/*/}
unset REF_BRANCH ; case ${GITHUB_REF} in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac;
unset REF_TAG ; case ${GITHUB_REF} in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
REF_SHAS=${GITHUB_SHA:0:8}
echo set-output name=REF_NAME::${REF_NAME}
echo set-output name=REF_BRANCH::${REF_BRANCH}
echo set-output name=REF_TAG::${REF_TAG}
echo set-output name=REF_SHAS::${REF_SHAS}
echo ::set-output name=REF_NAME::${REF_NAME}
echo ::set-output name=REF_BRANCH::${REF_BRANCH}
echo ::set-output name=REF_TAG::${REF_TAG}
echo ::set-output name=REF_SHAS::${REF_SHAS}
# parse target
unset TARGET_ARCH ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) TARGET_ARCH=arm ;; i686-*) TARGET_ARCH=i686 ;; x86_64-*) TARGET_ARCH=x86_64 ;; esac;
echo set-output name=TARGET_ARCH::${TARGET_ARCH}
echo ::set-output name=TARGET_ARCH::${TARGET_ARCH}
unset TARGET_OS ; case ${{ matrix.job.target }} in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac;
echo set-output name=TARGET_OS::${TARGET_OS}
echo ::set-output name=TARGET_OS::${TARGET_OS}
# package name
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }}
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
echo set-output name=PKG_suffix::${PKG_suffix}
echo set-output name=PKG_BASENAME::${PKG_BASENAME}
echo set-output name=PKG_NAME::${PKG_NAME}
echo ::set-output name=PKG_suffix::${PKG_suffix}
echo ::set-output name=PKG_BASENAME::${PKG_BASENAME}
echo ::set-output name=PKG_NAME::${PKG_NAME}
# deployable tag? (ie, leading "vM" or "M"; M == version number)
unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
echo set-output name=DEPLOY::${DEPLOY:-<empty>/false}
echo ::set-output name=DEPLOY::${DEPLOY}
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
echo set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
echo ::set-output name=CARGO_FEATURES_OPTION::${CARGO_FEATURES_OPTION}
# * CARGO_USE_CROSS (truthy)
CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
echo set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS:-<empty>/false}
echo ::set-output name=CARGO_USE_CROSS::${CARGO_USE_CROSS}
# # * `arm` cannot be tested on ubuntu-* hosts (b/c testing is currently primarily done via comparison of target outputs with built-in outputs and the `arm` target is not executable on the host)
JOB_DO_TESTING="true"
case ${{ matrix.job.target }} in arm-*) unset JOB_DO_TESTING ;; esac;
echo set-output name=JOB_DO_TESTING::${JOB_DO_TESTING:-<empty>/false}
echo ::set-output name=JOB_DO_TESTING::${JOB_DO_TESTING}
# # * test only binary for arm-type targets
unset CARGO_TEST_OPTIONS
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
echo set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
# * strip executable?
STRIP="strip" ; case ${{ matrix.job.target }} in arm-unknown-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; esac;
echo set-output name=STRIP::${STRIP}
echo ::set-output name=STRIP::${STRIP}
- name: Create all needed build/work directories
shell: bash
run: |
mkdir -p '${{ steps.vars.outputs.STAGING }}'
mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
- name: rust toolchain ~ install
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
target: ${{ matrix.job.target }}
override: true
profile: minimal # minimal component installation (ie, no documentation)
- name: Info
shell: bash
run: |
gcc --version || true
rustup -V
rustup toolchain list
rustup default
cargo -V
rustc -V
- name: Build
uses: actions-rs/cargo@v1
with:
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
command: build
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
- 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: Package
shell: bash
run: |
# binary
cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
# `strip` binary (if needed)
if [ -n "${{ steps.vars.outputs.STRIP }}" ]; then "${{ steps.vars.outputs.STRIP }}" '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' ; fi
# README and LICENSE
cp README.md '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
cp LICENSE '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
# base compressed package
pushd '${{ steps.vars.outputs.STAGING }}/' >/dev/null
case ${{ matrix.job.target }} in
*-pc-windows-*) 7z -y a '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* | tail -2 ;;
*) tar czf '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* ;;
esac;
popd >/dev/null
- name: Publish
uses: softprops/action-gh-release@v1
if: steps.vars.outputs.DEPLOY
with:
files: |
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
## 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 <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
# 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 <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
# 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 }}"

1
.gitignore vendored
View File

@@ -6,4 +6,3 @@
**/*.rs.bk
*.swp
.vscode/*
*.idea/*

76
.travis.yml Normal file
View File

@@ -0,0 +1,76 @@
# Based on the "trust" template v0.1.2
# https://github.com/japaric/trust/tree/v0.1.2
dist: trusty
language: rust
services: docker
sudo: required
# TODO Rust builds on stable by default, this can be
# overridden on a case by case basis down below.
env:
global:
# TODO Update this to match the name of your project.
- CRATE_NAME=dust
matrix:
# TODO These are all the build jobs. Adjust as necessary. Comment out what you
# don't need
include:
# Linux
- env: TARGET=x86_64-unknown-linux-gnu
# OSX
- env: TARGET=x86_64-apple-darwin
os: osx
before_install:
- set -e
- rustup self update
install:
- sh ci/install.sh
- source ~/.cargo/env || true
script:
- bash ci/script.sh
after_script: set +e
before_deploy:
- sh ci/before_deploy.sh
deploy:
# TODO update `api_key.secure`
# - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new
# - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789
# - Paste the output down here
api_key:
secure: UlU73Td7Bkb2N88ws4YGLWR+4U0IMgiou9QQtMnmpouJFjeUNxtLSPMPODVXP7zq4sKt5HR5B3fX9MW4mKm351fvnQEoihETn06pKiXGnY//SlTPTt67MX9ZOYmd9ohJReMDOZDgqhnGLxfymycGtsLAmdjDZnAl+IMqgg0FMyVFj9Cl9aKxnn12lxQyX4zabHKk8TUKD3By8ZoEUnJMHt3gEtOmbDgS4brcTPeHCzqnYFw73LEnkqvz+JP0XwauJY7Cf8lminKm/klmjCkQji8T9SHI52v1g0Fxpx0ucp2o3vulQrLHXaHvZ6Fr7J0cSXXzaFF3rrGLt4t4jU/+9TZm1+n5k5XuPW4x4NTCC9NmIj/z0/z41t82E9qZhzhtm2Jdsg6H2tNk+C774TYqcmR6GCvfRadfjRp3cA5dh0UwDVjH2MJFxlHDVkl6la0mVVRsCGF3oBKZVk0BDl1womfnmI46o/uU+gLknHN6Ed6PHHPPYDViWd3VKdmHKT7XrkMMUF6HjZUtla689DWIOWZSiV++1dVPcl/1TV+6tTmN4bBtPcLuX7SHRuLp2PI2kATvRMECsa7gZRypW4jKpVn7b2yetX9TVI3i1zR5zkQJ3dPg8sATvYPL53aKH/WsqUg4rzoAlbk9so+++R4bQY69LhV3B511B7EAynoZFdM
file_glob: true
file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.*
on:
# TODO Here you can pick which targets will generate binary releases
# In this example, there are some targets that are tested using the stable
# and nightly channels. This condition makes sure there is only one release
# for such targets and that's generated using the stable channel
condition: $TRAVIS_RUST_VERSION = stable
tags: true
provider: releases
skip_cleanup: true
cache: cargo
before_cache:
# Travis can't cache files that are not readable by "others"
- chmod -R a+r $HOME/.cargo
branches:
only:
# release tags
- /^v\d+\.\d+\.\d+.*$/
- master
notifications:
email:
on_success: never

690
Cargo.lock generated
View File

@@ -1,529 +1,511 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
name = "assert_cli"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "assert_cmd"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d20831bd004dda4c7c372c19cdabff369f794a95e955b3f13fe460e3e1ae95f"
dependencies = [
"bstr",
"doc-comment",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
"colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"environment 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"skeptic 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.14"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "autocfg"
version = "1.0.1"
name = "backtrace"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
dependencies = [
"backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace-sys"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.2.1"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bstr"
version = "0.2.16"
name = "bitflags"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bytecount"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cargo_metadata"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cc"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "1.0.0"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.33.3"
version = "2.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term 0.11.0",
"atty",
"bitflags",
"strsim",
"term_size",
"textwrap",
"unicode-width",
"vec_map",
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
name = "colored"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if",
"crossbeam-utils",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
name = "difference"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
name = "dtoa"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "du-dust"
version = "0.6.1"
version = "0.2.4"
dependencies = [
"ansi_term 0.12.1",
"assert_cmd",
"clap",
"lscolors",
"rayon",
"stfu8",
"tempfile",
"terminal_size",
"thousands",
"unicode-width",
"winapi-util",
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"assert_cli 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "either"
version = "1.6.1"
name = "environment"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "getrandom"
version = "0.2.3"
name = "error-chain"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
"backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itertools"
version = "0.10.1"
name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "glob"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "itoa"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [
"either",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.98"
version = "0.2.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "lscolors"
version = "0.7.1"
name = "num-traits"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
dependencies = [
"ansi_term 0.12.1",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.4.0"
name = "pulldown-cmark"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "memoffset"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
dependencies = [
"autocfg",
"bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
name = "quote"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "predicates"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6e46ca79eb4e21e2ec14430340c71250ab69332abf85521c95d3a8bc336aa76"
dependencies = [
"difflib",
"itertools",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
[[package]]
name = "predicates-tree"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2"
dependencies = [
"predicates-core",
"treeline",
"proc-macro2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.8.4"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]]
name = "rayon"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"lazy_static",
"num_cpus",
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.2.9"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
dependencies = [
"bitflags",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
name = "rustc-demangle"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "stfu8"
version = "0.2.4"
name = "same-file"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf70433e3300a3c395d06606a700cdf4205f4f14dbae2c6833127c6bb22db77"
dependencies = [
"lazy_static",
"regex",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "same-file"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive_internals 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive_internals"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "skeptic"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytecount 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cargo_metadata 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.8.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tempfile"
version = "3.2.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if",
"libc",
"rand",
"redox_syscall",
"remove_dir_all",
"winapi",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "term_size"
version = "0.3.2"
name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"term_size",
"unicode-width",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thousands"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
[[package]]
name = "treeline"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]]
name = "unicode-width"
version = "0.1.8"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "vec_map"
version = "0.8.2"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "wait-timeout"
version = "0.2.0"
name = "walkdir"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
name = "walkdir"
version = "2.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
dependencies = [
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.3.9"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[metadata]
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum assert_cli 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72342c21057a3cb5f7c2d849bf7999a83795434dd36d74fa8c24680581bd1930"
"checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
"checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2"
"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum bytecount 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "af27422163679dea46a1a7239dffff64d3dcdc3ba5fe9c49c789fbfe0eb949de"
"checksum cargo_metadata 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1f56ec3e469bca7c276f2eea015aa05c5e381356febdbb0683c2580189604537"
"checksum cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2b4911e4bdcb4100c7680e7e854ff38e23f1b34d4d9e079efae3da2801341ffc"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dc18f6f4005132120d9711636b32c46a233fad94df6217fa1d81c5e97a9f200"
"checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc"
"checksum difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3304d19798a8e067e48d8e69b2c37f0b5e9b4e462504ad9e27e9f3fce02bba8"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum environment 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f4b14e20978669064c33b4c1e0fb4083412e40fe56cbea2eae80fd7591503ee"
"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
"checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364"
"checksum proc-macro2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "681c2c8e039ff358cb926dbc5151d561cbd0249089986ace39dfe8e405bb7511"
"checksum pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdf85cda6cadfae5428a54661d431330b312bc767ddbc57adbedc24da66e32"
"checksum quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0ff51282f28dc1b53fd154298feaa2e77c5ea0dba68e1fd8b03b72fbe13d2a"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum remove_dir_all 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfc5b3ce5d5ea144bb04ebd093a9e14e9765bcfec866aecda9b6dec43b3d1e24"
"checksum rustc-demangle 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11fb43a206a04116ffd7cfcf9bcb941f8eb6cc7ff667272246b0a1c74259a3cb"
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
"checksum semver 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bee2bc909ab2d8d60dab26e8cad85b25d795b14603a0dcb627b78b9d30b6454b"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "d3bcee660dcde8f52c3765dd9ca5ee36b4bf35470a738eb0bd5a8752b0389645"
"checksum serde_derive 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "f1711ab8b208541fa8de00425f6a577d90f27bb60724d2bb5fd911314af9668f"
"checksum serde_derive_internals 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "89b340a48245bc03ddba31d0ff1709c118df90edc6adabaca4aac77aea181cce"
"checksum serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "5c508584d9913df116b91505eec55610a2f5b16e9ed793c46e4d0152872b3e74"
"checksum skeptic 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c8431f8fca168e2db4be547bd8329eac70d095dff1444fee4b0fa0fabc7df75a"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum tempfile 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "439d9a7c00f98b1b5ee730039bf5b1f9203d508690e3c76b509e7ad59f8f7c99"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
"checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -1,9 +1,8 @@
[package]
name = "du-dust"
description = "A more intuitive version of du"
version = "0.6.1"
version = "0.3.0"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2018"
documentation = "https://github.com/bootandy/dust"
homepage = "https://github.com/bootandy/dust"
@@ -14,29 +13,16 @@ categories = ["command-line-utilities"]
license = "Apache-2.0"
[badges]
travis-ci = { repository = "https://travis-ci.org/bootandy/dust" }
travis-ci = {repository = "https://travis-ci.org/bootandy/dust"}
[[bin]]
name = "dust"
path = "src/main.rs"
[dependencies]
ansi_term = "0.12"
clap = { version = "=2.33", features = ["wrap_help"] }
lscolors = "0.7"
terminal_size = "0.1"
unicode-width = "0.1"
rayon="1"
thousands = "0.2"
stfu8 = "0.2"
ansi_term = "0.11"
clap = "2.31"
assert_cli = "0.5"
tempfile = "3"
walkdir = "2"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"
[dev-dependencies]
assert_cmd = "1"
tempfile = "=3"
[[test]]
name = "integration"
path = "tests/tests.rs"

View File

@@ -3,68 +3,70 @@
# Dust
du + rust = dust. Like du but more intuitive.
# Why
Because I want an easy way to see where my disk is being used.
# Demo
![Example](media/snap.png)
du + rust = dust. Like du but more intuitive
## Install
#### Cargo <a href="https://repology.org/project/du-dust/versions"><img src="https://repology.org/badge/vertical-allrepos/du-dust.svg" alt="Packaging status" align="right"></a>
### Cargo
* `cargo install du-dust`
* cargo install du-dust
#### 🍺 Homebrew (Mac OS)
### Download
* `brew install dust`
#### 🍺 Homebrew (Linux)
* `brew tap tgotwig/linux-dust && brew install dust`
#### 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/`
* 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 is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust will print a maximum of 1 'Did not have permissions message'.
Dust will list a slightly-less-than-the-terminal-height number of the biggest subdirectories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest subdirectories will be colored.
Dust will list the 20 biggest sub directories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest sub directory will have its size shown in *red*
## Why?
du has a number of ways of showing you what it finds, in terms of disk consumption, but really, there are only one or two ways you invoke it: with -h for “human readable” units, like 100G or 89k, or with -b for “bytes”. The former is generally used for a quick survey of a directory with a small number of things in it, and the latter for when you have a bunch and need to sort the output numerically, and youre obligated to either further pass it into something like awk to turn bytes into the appropriate human-friendly unit like mega or gigabytes, or pipe thru sort and head while remembering the '-h' flag. Then once you have the top offenders, you recurse down into the largest one and repeat the process until youve found your cruft or gems and can move on.
Dust assumes thats what you wanted to do in the first place, and takes care of tracking the largest offenders in terms of actual size, and showing them to you with human-friendly units and in-context within the filetree.
## Usage
```
Usage: dust
Usage: dust <dir>
Usage: dust <dir> <another_dir> <and_more>
Usage: dust -p (full-path - Show fullpath of the subdirectories)
Usage: dust -s (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 (shows 30 directories instead of the default [default is terminal height])
Usage: dust -d 3 (shows 3 levels of subdirectories)
Usage: dust -r (reverse order of output)
Usage: dust -X ignore (ignore all files and directories with the name 'ignore')
Usage: dust -x (only show directories on the same filesystem)
Usage: dust -b (do not show percentages or draw ASCII bars)
Usage: dust -i (do not show hidden files)
Usage: dust -c (No colors [monochrome])
Usage: dust -f (Count files instead of diskspace)
Usage: dust -p <dir> (full-path - does not shorten the path of the subdirectories)
Usage: dust -s <dir> (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 <dir> (Shows 30 directories not 20)
Usage: dust -d 3 <dir> (Shows 3 levels of subdirectories)
```
```
djin:git/dust> dust
1.2G target
622M ├─┬ debug
445M │ ├── deps
70M │ ├── incremental
56M │ └── build
262M ├─┬ rls
262M │ └─┬ debug
203M │ ├── deps
56M │ └── build
165M ├─┬ package
165M │ └─┬ du-dust-0.2.4
165M │ └─┬ target
165M │ └─┬ debug
131M │ └── deps
165M └─┬ release
124M └── deps
```
## Performance
Dust is currently about 4 times slower than du.
## 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.

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env bash
# This script takes care of building your crate and packaging it for release
set -ex

View File

@@ -1,10 +0,0 @@
# ----------- To do a release ---------
# edit version in cargo.toml
# tag a commit and push (increment version in Cargo.toml first):
# git tag v0.4.5
# git push origin v0.4.5
# cargo publish to put it in crates.io
# To install locally [Do before pushing it]
#cargo install --path .

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env bash
set -ex
main() {

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env bash
# This script takes care of testing your crate
set -ex

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,172 +0,0 @@
use std::fs;
use crate::node::Node;
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use std::path::PathBuf;
use std::sync::atomic;
use std::sync::atomic::AtomicBool;
use std::collections::HashSet;
use crate::node::build_node;
use std::fs::DirEntry;
use crate::platform::get_metadata;
pub struct WalkData {
pub ignore_directories: HashSet<PathBuf>,
pub allowed_filesystems: HashSet<u64>,
pub use_apparent_size: bool,
pub by_filecount: bool,
pub ignore_hidden: bool,
}
pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: WalkData) -> (Vec<Node>, bool) {
let permissions_flag = AtomicBool::new(false);
let top_level_nodes: Vec<_> = dirs
.into_iter()
.filter_map(|d| {
let n = walk(d, &permissions_flag, &walk_data);
match n {
Some(n) => {
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
clean_inodes(n, &mut inodes, walk_data.use_apparent_size)
}
None => None,
}
})
.collect();
(top_level_nodes, permissions_flag.into_inner())
}
// Remove files which have the same inode, we don't want to double count them.
fn clean_inodes(
x: Node,
inodes: &mut HashSet<(u64, u64)>,
use_apparent_size: bool,
) -> Option<Node> {
if !use_apparent_size {
if let Some(id) = x.inode_device {
if inodes.contains(&id) {
return None;
}
inodes.insert(id);
}
}
let new_children: Vec<_> = x
.children
.into_iter()
.filter_map(|c| clean_inodes(c, inodes, use_apparent_size))
.collect();
return Some(Node {
name: x.name,
size: x.size + new_children.iter().map(|c| c.size).sum::<u64>(),
children: new_children,
inode_device: x.inode_device,
});
}
fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.');
let is_ignored_path = walk_data.ignore_directories.contains(&entry.path());
if !walk_data.allowed_filesystems.is_empty() {
let size_inode_device = get_metadata(&entry.path(), false);
if let Some((_size, Some((_id, dev)))) = size_inode_device {
if !walk_data.allowed_filesystems.contains(&dev) {
return true;
}
}
}
(is_dot_file && walk_data.ignore_hidden) || is_ignored_path
}
fn walk(dir: PathBuf, permissions_flag: &AtomicBool, walk_data: &WalkData) -> Option<Node> {
let mut children = vec![];
if let Ok(entries) = fs::read_dir(dir.clone()) {
children = entries
.into_iter()
.par_bridge()
.filter_map(|entry| {
if let Ok(ref entry) = entry {
// uncommenting the below line gives simpler code but
// rayon doesn't parallelise as well giving a 3X performance drop
// hence we unravel the recursion a bit
// return walk(entry.path(), permissions_flag, ignore_directories, allowed_filesystems, use_apparent_size, by_filecount, ignore_hidden);
if !ignore_file(&entry, walk_data) {
if let Ok(data) = entry.file_type() {
if data.is_dir() && !data.is_symlink() {
return walk(entry.path(), permissions_flag, walk_data);
}
return build_node(
entry.path(),
vec![],
walk_data.use_apparent_size,
data.is_symlink(),
walk_data.by_filecount,
);
}
}
} else {
permissions_flag.store(true, atomic::Ordering::Relaxed);
}
None
})
.collect();
} else {
permissions_flag.store(true, atomic::Ordering::Relaxed);
}
build_node(
dir,
children,
walk_data.use_apparent_size,
false,
walk_data.by_filecount,
)
}
mod tests {
#[allow(unused_imports)]
use super::*;
#[cfg(test)]
fn create_node() -> Node {
Node {
name: PathBuf::new(),
size: 10,
children: vec![],
inode_device: Some((5, 6)),
}
}
#[test]
fn test_should_ignore_file() {
let mut inodes = HashSet::new();
let n = create_node();
// First time we insert the node
assert!(clean_inodes(n.clone(), &mut inodes, false) == Some(n.clone()));
// Second time is a duplicate - we ignore it
assert!(clean_inodes(n.clone(), &mut inodes, false) == None);
}
#[test]
fn test_should_not_ignore_files_if_using_apparent_size() {
let mut inodes = HashSet::new();
let n = create_node();
// If using apparent size we include Nodes, even if duplicate inodes
assert!(clean_inodes(n.clone(), &mut inodes, true) == Some(n.clone()));
assert!(clean_inodes(n.clone(), &mut inodes, true) == Some(n.clone()));
}
}

View File

@@ -1,354 +1,162 @@
extern crate ansi_term;
use crate::display_node::DisplayNode;
use self::ansi_term::Colour::Red;
use lscolors::{LsColors, Style};
use unicode_width::UnicodeWidthStr;
use stfu8::encode_u8;
use std::cmp::max;
use std::cmp::min;
use std::fs;
use std::iter::repeat;
use std::path::Path;
use thousands::Separable;
use self::ansi_term::Colour::Fixed;
static UNITS: [char; 4] = ['T', 'G', 'M', 'K'];
static BLOCKS: [char; 5] = ['█', '▓', '▒', '░', ' '];
pub struct DisplayData {
pub short_paths: bool,
pub is_reversed: bool,
pub colors_on: bool,
pub by_filecount: bool,
pub num_chars_needed_on_left_most: usize,
pub base_size: u64,
pub longest_string_length: usize,
pub ls_colors: LsColors,
}
impl DisplayData {
fn get_tree_chars(&self, was_i_last: bool, has_children: bool) -> &'static str {
match (self.is_reversed, was_i_last, has_children) {
(true, true, true) => "┌─┴",
(true, true, false) => "┌──",
(true, false, true) => "├─┴",
(true, false, false) => "├──",
(false, true, true) => "└─┬",
(false, true, false) => "└──",
(false, false, true) => "├─┬",
(false, false, false) => "├──",
}
}
fn is_biggest(&self, num_siblings: usize, max_siblings: u64) -> bool {
if self.is_reversed {
num_siblings == (max_siblings - 1) as usize
} else {
num_siblings == 0
}
}
fn is_last(&self, num_siblings: usize, max_siblings: u64) -> bool {
if self.is_reversed {
num_siblings == 0
} else {
num_siblings == (max_siblings - 1) as usize
}
}
fn percent_size(&self, node: &DisplayNode) -> f32 {
let result = node.size as f32 / self.base_size as f32;
if result.is_normal() {
result
} else {
0.0
}
}
}
struct DrawData<'a> {
indent: String,
percent_bar: String,
display_data: &'a DisplayData,
}
impl DrawData<'_> {
fn get_new_indent(&self, has_children: bool, was_i_last: bool) -> String {
let chars = self.display_data.get_tree_chars(was_i_last, has_children);
self.indent.to_string() + chars
}
// TODO: can we test this?
fn generate_bar(&self, node: &DisplayNode, level: usize) -> String {
let chars_in_bar = self.percent_bar.chars().count();
let num_bars = chars_in_bar as f32 * self.display_data.percent_size(node);
let mut num_not_my_bar = (chars_in_bar as i32) - num_bars as i32;
let mut new_bar = "".to_string();
let idx = 5 - min(4, max(1, level));
for c in self.percent_bar.chars() {
num_not_my_bar -= 1;
if num_not_my_bar <= 0 {
new_bar.push(BLOCKS[0]);
} else if c == BLOCKS[0] {
new_bar.push(BLOCKS[idx]);
} else {
new_bar.push(c);
}
}
new_bar
}
}
#[allow(clippy::too_many_arguments)]
pub fn draw_it(
permission_error: bool,
use_full_path: bool,
is_reversed: bool,
no_colors: bool,
no_percents: bool,
terminal_width: usize,
by_filecount: bool,
option_root_node: Option<DisplayNode>,
) {
if permission_error {
permissions: bool,
short_paths: bool,
depth: Option<u64>,
base_dirs: Vec<String>,
to_display: Vec<(String, u64)>,
) -> () {
if !permissions {
eprintln!("Did not have permissions for all directories");
}
if option_root_node.is_none() {
return;
}
let root_node = option_root_node.unwrap();
let num_chars_needed_on_left_most = if by_filecount {
let max_size = root_node.children.iter().map(|n| n.size).fold(0, max);
max_size.separate_with_commas().chars().count()
} else {
5 // Under normal usage we need 5 chars to display the size of a directory
};
let terminal_width = terminal_width - 9 - num_chars_needed_on_left_most;
let num_indent_chars = 3;
let longest_string_length =
find_longest_dir_name(&root_node, num_indent_chars, terminal_width, !use_full_path);
let max_bar_length = if no_percents || longest_string_length >= terminal_width as usize {
0
} else {
terminal_width as usize - longest_string_length
};
let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect::<String>();
let display_data = DisplayData {
short_paths: !use_full_path,
is_reversed,
colors_on: !no_colors,
by_filecount,
num_chars_needed_on_left_most,
base_size: root_node.size,
longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(),
};
let draw_data = DrawData {
indent: "".to_string(),
percent_bar: first_size_bar,
display_data: &display_data,
};
display_node(root_node, &draw_data, true, true);
}
fn find_longest_dir_name(
node: &DisplayNode,
indent: usize,
terminal: usize,
long_paths: bool,
) -> usize {
let printable_name = get_printable_name(&node.name, long_paths);
let longest = min(
UnicodeWidthStr::width(&*printable_name) + 1 + indent,
terminal,
);
// each none root tree drawing is 2 more chars, hence we increment indent by 2
node.children
.iter()
.map(|c| find_longest_dir_name(c, indent + 2, terminal, long_paths))
.fold(longest, max)
}
fn display_node(node: DisplayNode, draw_data: &DrawData, is_biggest: bool, is_last: bool) {
// hacky way of working out how deep we are in the tree
let indent = draw_data.get_new_indent(!node.children.is_empty(), is_last);
let level = ((indent.chars().count() - 1) / 2) - 1;
let bar_text = draw_data.generate_bar(&node, level);
let to_print = format_string(
&node,
&*indent,
&*bar_text,
is_biggest,
draw_data.display_data,
);
if !draw_data.display_data.is_reversed {
println!("{}", to_print)
}
let dd = DrawData {
indent: clean_indentation_string(&*indent),
percent_bar: bar_text,
display_data: draw_data.display_data,
};
let num_siblings = node.num_siblings();
for (count, c) in node
.get_children_from_node(draw_data.display_data.is_reversed)
.enumerate()
{
let is_biggest = dd.display_data.is_biggest(count, num_siblings);
let was_i_last = dd.display_data.is_last(count, num_siblings);
display_node(c, &dd, is_biggest, was_i_last);
}
if draw_data.display_data.is_reversed {
println!("{}", to_print)
for f in base_dirs {
display_node(f, &to_display, true, short_paths, depth, "")
}
}
fn clean_indentation_string(s: &str) -> String {
let mut is: String = s.into();
// For reversed:
is = is.replace("┌─┴", " ");
is = is.replace("┌──", " ");
is = is.replace("├─┴", "");
is = is.replace("─┴", " ");
// For normal
is = is.replace("└─┬", " ");
is = is.replace("└──", " ");
is = is.replace("├─┬", "");
is = is.replace("─┬", " ");
// For both
is = is.replace("├──", "");
is
fn get_size(nodes: &Vec<(String, u64)>, node_to_print: &String) -> Option<u64> {
for &(ref k, ref v) in nodes.iter() {
if *k == *node_to_print {
return Some(*v);
}
}
None
}
fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool) -> String {
let dir_name = dir_name.as_ref();
let printable_name = {
if long_paths {
match dir_name.parent() {
Some(prefix) => match dir_name.strip_prefix(prefix) {
Ok(base) => base,
Err(_) => dir_name,
},
None => dir_name,
fn display_node<S: Into<String>>(
node_to_print: String,
to_display: &Vec<(String, u64)>,
is_biggest: bool,
short_paths: bool,
depth: Option<u64>,
indentation_str: S,
) {
let new_depth = match depth {
None => None,
Some(0) => return,
Some(d) => Some(d - 1),
};
match get_size(to_display, &node_to_print) {
None => println!("Can not find path: {}", node_to_print),
Some(size) => {
let mut is = indentation_str.into();
let ntp: &str = node_to_print.as_ref();
print_this_node(ntp, size, is_biggest, short_paths, is.as_ref());
is = is.replace("└─┬", " ");
is = is.replace("└──", " ");
is = is.replace("├──", "");
is = is.replace("├─┬", "");
let printable_node_slashes = node_to_print.matches('/').count();
let mut num_siblings = to_display.iter().fold(0, |a, b| {
if b.0.starts_with(ntp) && b.0.matches('/').count() == printable_node_slashes + 1 {
a + 1
} else {
a
}
});
let mut is_biggest = true;
for &(ref k, _) in to_display.iter() {
if k.starts_with(ntp) && k.matches('/').count() == printable_node_slashes + 1 {
num_siblings -= 1;
let mut has_children = false;
if new_depth.is_none() || new_depth.unwrap() != 1 {
for &(ref k2, _) in to_display.iter() {
let kk: &str = k.as_ref();
if k2.starts_with(kk)
&& k2.matches('/').count() == printable_node_slashes + 2
{
has_children = true;
}
}
}
display_node(
k.to_string(),
to_display,
is_biggest,
short_paths,
new_depth,
is.to_string() + get_tree_chars(num_siblings, has_children),
);
is_biggest = false;
}
}
}
}
}
fn get_tree_chars(num_siblings: u64, has_children: bool) -> &'static str {
if num_siblings == 0 {
if has_children {
"└─┬"
} else {
"└──"
}
} else {
if has_children {
"├─┬"
} else {
"├──"
}
}
}
fn print_this_node(
node_name: &str,
size: u64,
is_biggest: bool,
short_paths: bool,
indentation: &str,
) {
let pretty_size = format!("{:>5}", human_readable_number(size),);
println!(
"{}",
format_string(
node_name,
is_biggest,
short_paths,
pretty_size.as_ref(),
indentation
)
)
}
pub fn format_string(
dir_name: &str,
is_biggest: bool,
short_paths: bool,
size: &str,
indentation: &str,
) -> String {
let printable_name = {
if short_paths && dir_name.contains('/') {
dir_name.split('/').last().unwrap()
} else {
dir_name
}
};
encode_u8(printable_name.display().to_string().as_bytes())
}
fn pad_or_trim_filename(node: &DisplayNode, indent: &str, display_data: &DisplayData) -> String {
let name = get_printable_name(&node.name, display_data.short_paths);
let indent_and_name = format!("{} {}", indent, name);
let width = UnicodeWidthStr::width(&*indent_and_name);
assert!(display_data.longest_string_length >= width);
// Add spaces after the filename so we can draw the % used bar chart.
let name_and_padding = name
+ &(repeat(" ")
.take(display_data.longest_string_length - width)
.collect::<String>());
maybe_trim_filename(name_and_padding, display_data)
}
fn maybe_trim_filename(name_in: String, display_data: &DisplayData) -> String {
if UnicodeWidthStr::width(&*name_in) > display_data.longest_string_length {
let name = name_in
.chars()
.take(display_data.longest_string_length - 2)
.collect::<String>();
name + ".."
} else {
name_in
}
}
pub fn format_string(
node: &DisplayNode,
indent: &str,
percent_bar: &str,
is_biggest: bool,
display_data: &DisplayData,
) -> String {
let (percents, name_and_padding) = get_name_percent(node, indent, percent_bar, display_data);
let pretty_size = get_pretty_size(node, is_biggest, display_data);
let pretty_name = get_pretty_name(node, name_and_padding, display_data);
format!("{} {} {}{}", pretty_size, indent, pretty_name, percents)
}
fn get_name_percent(
node: &DisplayNode,
indent: &str,
bar_chart: &str,
display_data: &DisplayData,
) -> (String, String) {
if !bar_chart.is_empty() {
let percent_size_str = format!("{:.0}%", display_data.percent_size(node) * 100.0);
let percents = format!("{}{:>4}", bar_chart, percent_size_str);
let name_and_padding = pad_or_trim_filename(node, indent, display_data);
(percents, name_and_padding)
} else {
let n = get_printable_name(&node.name, display_data.short_paths);
let name = maybe_trim_filename(n, display_data);
("".into(), name)
}
}
fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayData) -> String {
let output = if display_data.by_filecount {
let size_as_str = node.size.separate_with_commas();
let spaces_to_add =
display_data.num_chars_needed_on_left_most - size_as_str.chars().count();
size_as_str + &*repeat(' ').take(spaces_to_add).collect::<String>()
} else {
format!("{:>5}", human_readable_number(node.size))
};
if is_biggest && display_data.colors_on {
format!("{}", Red.paint(output))
} else {
output
}
}
fn get_pretty_name(
node: &DisplayNode,
name_and_padding: String,
display_data: &DisplayData,
) -> String {
if display_data.colors_on {
let meta_result = fs::metadata(node.name.clone());
let directory_color = display_data
.ls_colors
.style_for_path_with_metadata(node.name.clone(), meta_result.as_ref().ok());
let ansi_style = directory_color
.map(Style::to_ansi_term_style)
.unwrap_or_default();
format!("{}", ansi_style.paint(name_and_padding))
} else {
name_and_padding
}
format!(
"{} {} {}",
if is_biggest {
Fixed(196).paint(size)
} else {
Fixed(7).paint(size)
},
indentation,
printable_name,
)
}
fn human_readable_number(size: u64) -> String {
@@ -368,63 +176,6 @@ fn human_readable_number(size: u64) -> String {
mod tests {
#[allow(unused_imports)]
use super::*;
#[allow(unused_imports)]
use std::path::PathBuf;
#[cfg(test)]
fn get_fake_display_data(longest_string_length: usize) -> DisplayData {
DisplayData {
short_paths: true,
is_reversed: false,
colors_on: false,
by_filecount: false,
num_chars_needed_on_left_most: 5,
base_size: 1,
longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(),
}
}
#[test]
fn test_format_str() {
let n = DisplayNode {
name: PathBuf::from("/short"),
size: 2_u64.pow(12), // This is 4.0K
children: vec![],
};
let indent = "┌─┴";
let percent_bar = "";
let is_biggest = false;
let s = format_string(
&n,
indent,
percent_bar,
is_biggest,
&get_fake_display_data(6),
);
assert_eq!(s, " 4.0K ┌─┴ short");
}
#[test]
fn test_format_str_long_name() {
let name = "very_long_name_longer_than_the_eighty_character_limit_very_long_name_this_bit_will_truncate";
let n = DisplayNode {
name: PathBuf::from(name),
size: 2_u64.pow(12), // This is 4.0K
children: vec![],
};
let indent = "┌─┴";
let percent_bar = "";
let is_biggest = false;
let dd = get_fake_display_data(64);
let s = format_string(&n, indent, percent_bar, is_biggest, &dd);
assert_eq!(
s,
" 4.0K ┌─┴ very_long_name_longer_than_the_eighty_character_limit_very_lon.."
);
}
#[test]
fn test_human_readable_number() {

View File

@@ -1,46 +0,0 @@
use std::cmp::Ordering;
use std::path::PathBuf;
#[derive(Debug, Eq, Clone)]
pub struct DisplayNode {
pub name: PathBuf, //todo: consider moving to a string?
pub size: u64,
pub children: Vec<DisplayNode>,
}
impl Ord for DisplayNode {
fn cmp(&self, other: &Self) -> Ordering {
if self.size == other.size {
self.name.cmp(&other.name)
} else {
self.size.cmp(&other.size)
}
}
}
impl PartialOrd for DisplayNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for DisplayNode {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.size == other.size && self.children == other.children
}
}
impl DisplayNode {
pub fn num_siblings(&self) -> u64 {
self.children.len() as u64
}
pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = DisplayNode> {
if is_reversed {
let children: Vec<DisplayNode> = self.children.clone().into_iter().rev().collect();
children.into_iter()
} else {
self.children.clone().into_iter()
}
}
}

View File

@@ -1,104 +0,0 @@
use crate::display_node::DisplayNode;
use crate::node::Node;
use std::collections::BinaryHeap;
use std::collections::HashSet;
use std::path::PathBuf;
pub fn get_by_depth(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode> {
if top_level_nodes.is_empty() {
// perhaps change this, bring back Error object?
return None;
}
let root = get_new_root(top_level_nodes);
Some(build_by_depth(&root, n - 1))
}
pub fn get_biggest(top_level_nodes: Vec<Node>, n: usize) -> Option<DisplayNode> {
if top_level_nodes.is_empty() {
// perhaps change this, bring back Error object?
return None;
}
let mut heap = BinaryHeap::new();
let number_top_level_nodes = top_level_nodes.len();
let root = get_new_root(top_level_nodes);
root.children.iter().for_each(|c| heap.push(c));
let mut allowed_nodes = HashSet::new();
allowed_nodes.insert(&root.name);
for _ in number_top_level_nodes..n {
let line = heap.pop();
match line {
Some(line) => {
line.children.iter().for_each(|c| heap.push(c));
allowed_nodes.insert(&line.name);
}
None => break,
}
}
recursive_rebuilder(&allowed_nodes, &root)
}
fn build_by_depth(node: &Node, depth: usize) -> DisplayNode {
let new_children = {
if depth == 0 {
vec![]
} else {
let mut new_children: Vec<_> = node
.children
.iter()
.map(|c| build_by_depth(c, depth - 1))
.collect();
new_children.sort();
new_children.reverse();
new_children
}
};
DisplayNode {
name: node.name.clone(),
size: node.size,
children: new_children,
}
}
fn get_new_root(top_level_nodes: Vec<Node>) -> Node {
if top_level_nodes.len() > 1 {
let total_size = top_level_nodes.iter().map(|node| node.size).sum();
Node {
name: PathBuf::from("(total)"),
size: total_size,
children: top_level_nodes,
inode_device: None,
}
} else {
top_level_nodes.into_iter().next().unwrap()
}
}
fn recursive_rebuilder<'a>(
allowed_nodes: &'a HashSet<&PathBuf>,
current: &Node,
) -> Option<DisplayNode> {
let mut new_children: Vec<_> = current
.children
.iter()
.filter_map(|c| {
if allowed_nodes.contains(&c.name) {
recursive_rebuilder(allowed_nodes, c)
} else {
None
}
})
.collect();
new_children.sort();
new_children.reverse();
let newnode = DisplayNode {
name: current.name.clone(),
size: current.size,
children: new_children,
};
Some(newnode)
}

View File

@@ -1,245 +1,98 @@
#[macro_use]
extern crate clap;
extern crate rayon;
extern crate unicode_width;
use std::collections::HashSet;
extern crate assert_cli;
extern crate walkdir;
use self::display::draw_it;
use clap::{App, AppSettings, Arg};
use dir_walker::walk_it;
use dir_walker::WalkData;
use filter::{get_biggest, get_by_depth};
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;
use std::io::{self, Write};
use utils::{find_big_ones, get_dir_tree, sort};
mod dir_walker;
mod display;
mod display_node;
mod filter;
mod node;
mod platform;
mod utils;
static DEFAULT_NUMBER_OF_LINES: usize = 30;
static DEFAULT_TERMINAL_WIDTH: usize = 80;
#[cfg(windows)]
fn init_color(no_color: bool) -> bool {
// 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))]
fn init_color(no_color: bool) -> bool {
no_color
}
fn get_height_of_terminal() -> usize {
// Windows CI runners detect a terminal height of 0
if let Some((Width(_w), Height(h))) = terminal_size() {
max(h as usize, DEFAULT_NUMBER_OF_LINES) - 10
} else {
DEFAULT_NUMBER_OF_LINES - 10
}
}
fn get_width_of_terminal() -> usize {
// Windows CI runners detect a very low terminal width
if let Some((Width(w), Height(_h))) = terminal_size() {
max(w as usize, DEFAULT_TERMINAL_WIDTH)
} else {
DEFAULT_TERMINAL_WIDTH
}
}
static DEFAULT_NUMBER_OF_LINES: &'static str = "20";
fn main() {
let default_height = get_height_of_terminal();
let def_num_str = default_height.to_string();
let options = App::new("Dust")
.about("Like du but more intuitive")
.version(crate_version!())
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name("depth")
.short("d")
.long("depth")
.help("Depth to show")
.takes_value(true)
.conflicts_with("number_of_lines"),
.takes_value(true),
)
.arg(
Arg::with_name("number_of_lines")
.short("n")
.long("number-of-lines")
.help("Number of lines of output to show. This is Height, (but h is help)")
.help("Number of lines of output to show")
.takes_value(true)
.default_value(def_num_str.as_ref()),
.default_value(DEFAULT_NUMBER_OF_LINES),
)
.arg(
Arg::with_name("display_full_paths")
.short("p")
.long("full-paths")
.help("Subdirectories will not have their path shortened"),
)
.arg(
Arg::with_name("ignore_directory")
.short("X")
.long("ignore-directory")
.takes_value(true)
.number_of_values(1)
.multiple(true)
.help("Exclude any file or directory with this name"),
)
.arg(
Arg::with_name("limit_filesystem")
.short("x")
.long("limit-filesystem")
.help("Only count the files and directories on the same filesystem as the supplied directory"),
.help("If set sub directories will not have their path shortened"),
)
.arg(
Arg::with_name("display_apparent_size")
.short("s")
.long("apparent-size")
.help("Use file length instead of blocks"),
)
.arg(
Arg::with_name("reverse")
.short("r")
.long("reverse")
.help("Print tree upside down (biggest highest)"),
)
.arg(
Arg::with_name("no_colors")
.short("c")
.long("no-colors")
.help("No colors will be printed (normally largest directories are colored)"),
)
.arg(
Arg::with_name("no_bars")
.short("b")
.long("no-percent-bars")
.help("No percent bars or percentages will be displayed"),
)
.arg(
Arg::with_name("by_filecount")
.short("f")
.long("filecount")
.help("Directory 'size' is number of child files/dirs not disk size"),
)
.arg(
Arg::with_name("ignore_hidden")
.short("i") // Do not use 'h' this is used by 'help'
.long("ignore_hidden")
.help("Do not display hidden files"),
)
.arg(
Arg::with_name("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"),
.help("If set will use file length. Otherwise we use blocks"),
)
.arg(Arg::with_name("inputs").multiple(true))
.get_matches();
let target_dirs = {
let filenames = {
match options.values_of("inputs") {
None => vec!["."],
Some(r) => r.collect(),
}
};
let number_of_lines = match value_t!(options.value_of("number_of_lines"), usize) {
Ok(v) => v,
Err(_) => {
eprintln!("Ignoring bad value for number_of_lines");
default_height
}
};
let terminal_width = match value_t!(options.value_of("width"), usize) {
Ok(v) => v,
Err(_) => get_width_of_terminal(),
};
let depth = options.value_of("depth").and_then(|depth| {
depth
.parse::<usize>()
.map(|v| v + 1)
.map_err(|_| eprintln!("Ignoring bad value for depth"))
.ok()
});
let no_colors = init_color(options.is_present("no_colors"));
let use_apparent_size = options.is_present("display_apparent_size");
let ignore_directories: Vec<PathBuf> = options
.values_of("ignore_directory")
.map(|i| i.map(PathBuf::from).collect())
.unwrap_or_default();
let by_filecount = options.is_present("by_filecount");
let ignore_hidden = options.is_present("ignore_hidden");
let limit_filesystem = options.is_present("limit_filesystem");
let simplified_dirs = simplify_dir_names(target_dirs);
let allowed_filesystems = {
if limit_filesystem {
get_filesystem_devices(simplified_dirs.iter())
let number_of_lines = value_t!(options.value_of("number_of_lines"), usize).unwrap();
let depth = {
if options.is_present("depth") {
match value_t!(options.value_of("depth"), u64) {
Ok(v) => Some(v + 1),
Err(_) => None,
}
} else {
HashSet::new()
None
}
};
if options.is_present("depth")
&& options.value_of("number_of_lines").unwrap() != DEFAULT_NUMBER_OF_LINES
{
io::stderr()
.write(b"Use either -n for number of directories to show. Or -d for depth. Not both")
.expect("Error writing to stderr. Oh the irony!");
return;
}
let ignored_full_path: HashSet<PathBuf> = ignore_directories
.into_iter()
.flat_map(|x| simplified_dirs.iter().map(move |d| d.join(x.clone())))
.collect();
let use_apparent_size = options.is_present("display_apparent_size");
let use_full_path = options.is_present("display_full_paths");
let walk_data = WalkData {
ignore_directories: ignored_full_path,
allowed_filesystems,
use_apparent_size,
by_filecount,
ignore_hidden,
};
let (nodes, errors) = walk_it(simplified_dirs, walk_data);
let tree = {
match depth {
None => get_biggest(nodes, number_of_lines),
Some(depth) => get_by_depth(nodes, depth),
let (permissions, nodes, top_level_names) = get_dir_tree(&filenames, use_apparent_size);
let sorted_data = sort(nodes);
let biggest_ones = {
if depth.is_none() {
find_big_ones(sorted_data, number_of_lines)
} else {
sorted_data
}
};
draw_it(
errors,
options.is_present("display_full_paths"),
!options.is_present("reverse"),
no_colors,
options.is_present("no_bars"),
terminal_width,
by_filecount,
tree,
permissions,
!use_full_path,
depth,
top_level_names,
biggest_ones,
);
}
#[cfg(test)]
mod tests;

View File

@@ -1,62 +0,0 @@
use crate::platform::get_metadata;
use std::cmp::Ordering;
use std::path::PathBuf;
#[derive(Debug, Eq, Clone)]
pub struct Node {
pub name: PathBuf,
pub size: u64,
pub children: Vec<Node>,
pub inode_device: Option<(u64, u64)>,
}
pub fn build_node(
dir: PathBuf,
children: Vec<Node>,
use_apparent_size: bool,
is_symlink: bool,
by_filecount: bool,
) -> Option<Node> {
match get_metadata(&dir, use_apparent_size) {
Some(data) => {
let (size, inode_device) = if by_filecount {
(1, data.1)
} else if is_symlink && !use_apparent_size {
(0, None)
} else {
data
};
Some(Node {
name: dir,
size,
children,
inode_device,
})
}
None => None,
}
}
impl PartialEq for Node {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.size == other.size && self.children == other.children
}
}
impl Ord for Node {
fn cmp(&self, other: &Self) -> Ordering {
if self.size == other.size {
self.name.cmp(&other.name)
} else {
self.size.cmp(&other.size)
}
}
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

View File

@@ -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 MetatdataExt seem to define a block as 512 bytes
// https://doc.rust-lang.org/std/os/linux/fs/trait.MetadataExt.html#tymethod.st_blocks
512
}
#[cfg(target_family = "unix")]
pub fn get_metadata(d: &Path, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::unix::fs::MetadataExt;
match d.metadata() {
Ok(md) => {
if use_apparent_size {
Some((md.len(), Some((md.ino(), md.dev()))))
} else {
Some((md.blocks() * get_block_size(), Some((md.ino(), md.dev()))))
}
}
Err(_e) => None,
}
}
#[cfg(target_family = "windows")]
pub fn get_metadata(d: &Path, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
// On windows opening the file to get size, file ID and volume can be very
// expensive because 1) it causes a few system calls, and more importantly 2) it can cause
// windows defender to scan the file.
// Therefore we try to avoid doing that for common cases, mainly those of
// plain files:
// The idea is to make do with the file size that we get from the OS for
// free as part of iterating a folder. Therefore we want to make sure that
// it makes sense to use that free size information:
// Volume boundaries:
// The user can ask us not to cross volume boundaries. If the DirEntry is a
// plain file and not a reparse point or other non-trivial stuff, we assume
// that the file is located on the same volume as the directory that
// contains it.
// File ID:
// This optimization does deprive us of access to a file ID. As a
// workaround, we just make one up that hopefully does not collide with real
// file IDs.
// Hard links: Unresolved. We don't get inode/file index, so hard links
// count once for each link. Hopefully they are not too commonly in use on
// windows.
// Size:
// We assume (naively?) that for the common cases the free size info is the
// same as one would get by doing the expensive thing. Sparse, encrypted and
// compressed files are not included in the common cases, as one can image
// there being more than view on their size.
// Savings in orders of magnitude in terms of time, io and cpu have been
// observed on hdd, windows 10, some 100Ks files taking up some hundreds of
// GBs:
// Consistently opening the file: 30 minutes.
// With this optimization: 8 sec.
use std::io;
use winapi_util::Handle;
fn handle_from_path_limited<P: AsRef<Path>>(path: P) -> io::Result<Handle> {
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
const FILE_READ_ATTRIBUTES: u32 = 0x0080;
// So, it seems that it does does have to be that expensive to open
// files to get their info: Avoiding opening the file with the full
// GENERIC_READ is key:
// https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights:
// "For example, a Windows file object maps the GENERIC_READ bit to the
// READ_CONTROL and SYNCHRONIZE standard access rights and to the
// FILE_READ_DATA, FILE_READ_EA, and FILE_READ_ATTRIBUTES
// object-specific access rights"
// The flag FILE_READ_DATA seems to be the expensive one, so we'll avoid
// that, and a most of the other ones. Simply because it seems that we
// don't need them.
let file = OpenOptions::new()
.access_mode(FILE_READ_ATTRIBUTES)
.open(path)?;
Ok(Handle::from_file(file))
}
fn get_metadata_expensive(d: &Path) -> Option<(u64, Option<(u64, u64)>)> {
use winapi_util::file::information;
let h = handle_from_path_limited(d).ok()?;
let info = information(&h).ok()?;
Some((
info.file_size(),
Some((info.file_index(), info.volume_serial_number())),
))
}
use std::os::windows::fs::MetadataExt;
match d.metadata() {
Ok(ref md) => {
const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20u32;
const FILE_ATTRIBUTE_READONLY: u32 = 0x1u32;
const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2u32;
const FILE_ATTRIBUTE_SYSTEM: u32 = 0x4u32;
const FILE_ATTRIBUTE_NORMAL: u32 = 0x80u32;
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10u32;
let attr_filtered = md.file_attributes()
& !(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
if attr_filtered == FILE_ATTRIBUTE_ARCHIVE
|| attr_filtered == FILE_ATTRIBUTE_DIRECTORY
|| md.file_attributes() == FILE_ATTRIBUTE_NORMAL
{
Some((md.len(), None))
} else {
get_metadata_expensive(&d)
}
}
_ => get_metadata_expensive(&d),
}
}

274
src/tests.rs Normal file
View File

@@ -0,0 +1,274 @@
extern crate ansi_term;
extern crate tempfile;
use self::tempfile::Builder;
use self::tempfile::TempDir;
use super::*;
use display::format_string;
use std::fs::File;
use std::io::Write;
use std::panic;
use std::path::PathBuf;
use std::process::Command;
#[test]
pub fn test_main() {
assert_cli::Assert::main_binary()
.with_args(&["src/test_dir"])
.stdout()
.is(main_output(true))
.unwrap();
}
#[test]
pub fn test_main_long_paths() {
assert_cli::Assert::main_binary()
.with_args(&["-p", "src/test_dir"])
.stdout()
.is(main_output(false))
.unwrap();
}
#[cfg(target_os = "macos")]
fn main_output(short_paths: bool) -> String {
format!(
"{}
{}
{}
{}",
format_string("src/test_dir", true, short_paths, " 4.0K", ""),
format_string("src/test_dir/many", true, short_paths, " 4.0K", "└─┬",),
format_string(
"src/test_dir/many/hello_file",
true,
short_paths,
" 4.0K",
" ├──",
),
format_string(
"src/test_dir/many/a_file",
false,
short_paths,
" 0B",
" └──",
),
)
}
#[cfg(target_os = "linux")]
fn main_output(short_paths: bool) -> String {
format!(
"{}
{}
{}
{}",
format_string("src/test_dir", true, short_paths, " 12K", ""),
format_string("src/test_dir/many", true, short_paths, " 8.0K", "└─┬",),
format_string(
"src/test_dir/many/hello_file",
true,
short_paths,
" 4.0K",
" ├──",
),
format_string(
"src/test_dir/many/a_file",
false,
short_paths,
" 0B",
" └──",
),
)
}
#[test]
pub fn test_apparent_size() {
let r = format!(
"{}",
format_string(
"src/test_dir/many/hello_file",
true,
true,
" 6B",
" ├──",
),
);
assert_cli::Assert::main_binary()
.with_args(&["-s", "src/test_dir"])
.stdout()
.contains(r)
.unwrap();
}
fn build_temp_file(dir: &TempDir) -> (PathBuf) {
let file_path = dir.path().join("notes.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "I am a temp file").unwrap();
file_path
}
#[test]
pub fn test_soft_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let file = build_temp_file(&dir);
let dir_s = dir.path().to_str().unwrap();
let file_path_s = file.to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg("-s")
.arg(file_path_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let r = soft_sym_link_output(dir_s, file_path_s, link_name_s);
// We cannot guarantee which version will appear first.
// TODO: Consider adding predictable itteration order (sort file entries by name?)
assert_cli::Assert::main_binary()
.with_args(&[dir_s])
.stdout()
.contains(r)
.unwrap();
}
#[cfg(target_os = "macos")]
fn soft_sym_link_output(dir: &str, file_path: &str, link_name: &str) -> String {
format!(
"{}
{}
{}",
format_string(dir, true, true, " 8.0K", ""),
format_string(file_path, true, true, " 4.0K", "├──",),
format_string(link_name, false, true, " 4.0K", "└──",),
)
}
#[cfg(target_os = "linux")]
fn soft_sym_link_output(dir: &str, file_path: &str, link_name: &str) -> String {
format!(
"{}
{}
{}",
format_string(dir, true, true, " 8.0K", ""),
format_string(file_path, true, true, " 4.0K", "├──",),
format_string(link_name, false, true, " 0B", "└──",),
)
}
// Hard links are ignored as the inode is the same as the file
#[test]
pub fn test_hard_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let file = build_temp_file(&dir);
let dir_s = dir.path().to_str().unwrap();
let file_path_s = file.to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg(file_path_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let (r, r2) = hard_link_output(dir_s, file_path_s, link_name_s);
// Because this is a hard link the file and hard link look identicle. Therefore
// we cannot guarantee which version will appear first.
// TODO: Consider adding predictable itteration order (sort file entries by name?)
let result = panic::catch_unwind(|| {
assert_cli::Assert::main_binary()
.with_args(&[dir_s])
.stdout()
.contains(r)
.unwrap();
});
if result.is_err() {
assert_cli::Assert::main_binary()
.with_args(&[dir_s])
.stdout()
.contains(r2)
.unwrap();
}
}
#[cfg(target_os = "macos")]
fn hard_link_output(dir_s: &str, file_path_s: &str, link_name_s: &str) -> (String, String) {
let r = format!(
"{}
{}",
format_string(dir_s, true, true, " 4.0K", ""),
format_string(file_path_s, true, true, " 4.0K", "└──")
);
let r2 = format!(
"{}
{}",
format_string(dir_s, true, true, " 4.0K", ""),
format_string(link_name_s, true, true, " 4.0K", "└──")
);
(r, r2)
}
#[cfg(target_os = "linux")]
fn hard_link_output(dir_s: &str, file_path_s: &str, link_name_s: &str) -> (String, String) {
let r = format!(
"{}
{}",
format_string(dir_s, true, true, " 8.0K", ""),
format_string(file_path_s, true, true, " 4.0K", "└──")
);
let r2 = format!(
"{}
{}",
format_string(dir_s, true, true, " 8.0K", ""),
format_string(link_name_s, true, true, " 4.0K", "└──")
);
(r, r2)
}
//Check we don't recurse down an infinite symlink tree
#[test]
pub fn test_recursive_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let dir_s = dir.path().to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg("-s")
.arg(dir_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
assert_cli::Assert::main_binary()
.with_args(&[dir_s])
.stdout()
.contains(recursive_sym_link_output(dir_s, link_name_s))
.unwrap();
}
#[cfg(target_os = "macos")]
fn recursive_sym_link_output(dir: &str, link_name: &str) -> String {
format!(
"{}
{}",
format_string(dir, true, true, " 4.0K", ""),
format_string(link_name, true, true, " 4.0K", "└──",),
)
}
#[cfg(target_os = "linux")]
fn recursive_sym_link_output(dir: &str, link_name: &str) -> String {
format!(
"{}
{}",
format_string(dir, true, true, " 4.0K", ""),
format_string(link_name, true, true, " 0B", "└──",),
)
}
// TODO: add test for bad path

View File

@@ -1,145 +0,0 @@
use platform::get_metadata;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use crate::platform;
fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
let parent = parent.as_ref();
let child = child.as_ref();
child.starts_with(parent) && !parent.starts_with(child)
}
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
let mut top_level_names: HashSet<PathBuf> = HashSet::with_capacity(filenames.len());
let mut to_remove: Vec<PathBuf> = Vec::with_capacity(filenames.len());
for t in filenames {
let top_level_name = normalize_path(t);
let mut can_add = true;
for tt in top_level_names.iter() {
if is_a_parent_of(&top_level_name, tt) {
to_remove.push(tt.to_path_buf());
} else if is_a_parent_of(tt, &top_level_name) {
can_add = false;
}
}
to_remove.sort_unstable();
top_level_names.retain(|tr| to_remove.binary_search(tr).is_err());
to_remove.clear();
if can_add {
top_level_names.insert(top_level_name);
}
}
top_level_names
}
pub fn get_filesystem_devices<'a, P: IntoIterator<Item = &'a PathBuf>>(paths: P) -> HashSet<u64> {
// Gets the device ids for the filesystems which are used by the argument paths
paths
.into_iter()
.filter_map(|p| {
let meta = get_metadata(&p, false);
if let Some((_size, Some((_id, dev)))) = meta {
Some(dev)
} else {
None
}
})
.collect()
}
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
// normalize path ...
// 1. removing repeated separators
// 2. removing interior '.' ("current directory") path segments
// 3. removing trailing extra separators and '.' ("current directory") path segments
// * `Path.components()` does all the above work; ref: <https://doc.rust-lang.org/std/path/struct.Path.html#method.components>
// 4. changing to os preferred separator (automatically done by recollecting components back into a PathBuf)
path.as_ref().components().collect::<PathBuf>()
}
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_simplify_dir() {
let mut correct = HashSet::new();
correct.insert(PathBuf::from("a"));
assert_eq!(simplify_dir_names(vec!["a"]), correct);
}
#[test]
fn test_simplify_dir_rm_subdir() {
let mut correct = HashSet::new();
correct.insert(["a", "b"].iter().collect::<PathBuf>());
assert_eq!(simplify_dir_names(vec!["a/b", "a/b/c", "a/b/d/f"]), correct);
}
#[test]
fn test_simplify_dir_duplicates() {
let mut correct = HashSet::new();
correct.insert(["a", "b"].iter().collect::<PathBuf>());
correct.insert(PathBuf::from("c"));
assert_eq!(
simplify_dir_names(vec![
"a/b",
"a/b//",
"a/././b///",
"c",
"c/",
"c/.",
"c/././",
"c/././."
]),
correct
);
}
#[test]
fn test_simplify_dir_rm_subdir_and_not_substrings() {
let mut correct = HashSet::new();
correct.insert(PathBuf::from("b"));
correct.insert(["c", "a", "b"].iter().collect::<PathBuf>());
correct.insert(["a", "b"].iter().collect::<PathBuf>());
assert_eq!(simplify_dir_names(vec!["a/b", "c/a/b/", "b"]), correct);
}
#[test]
fn test_simplify_dir_dots() {
let mut correct = HashSet::new();
correct.insert(PathBuf::from("src"));
assert_eq!(simplify_dir_names(vec!["src/."]), correct);
}
#[test]
fn test_simplify_dir_substring_names() {
let mut correct = HashSet::new();
correct.insert(PathBuf::from("src"));
correct.insert(PathBuf::from("src_v2"));
assert_eq!(simplify_dir_names(vec!["src/", "src_v2"]), correct);
}
#[test]
fn test_is_a_parent_of() {
assert!(is_a_parent_of("/usr", "/usr/andy"));
assert!(is_a_parent_of("/usr", "/usr/andy/i/am/descendant"));
assert!(!is_a_parent_of("/usr", "/usr/."));
assert!(!is_a_parent_of("/usr", "/usr/"));
assert!(!is_a_parent_of("/usr", "/usr"));
assert!(!is_a_parent_of("/usr/", "/usr"));
assert!(!is_a_parent_of("/usr/andy", "/usr"));
assert!(!is_a_parent_of("/usr/andy", "/usr/sibling"));
assert!(!is_a_parent_of("/usr/folder", "/usr/folder_not_a_child"));
}
#[test]
fn test_is_a_parent_of_root() {
assert!(is_a_parent_of("/", "/usr/andy"));
assert!(is_a_parent_of("/", "/usr"));
assert!(!is_a_parent_of("/", "/"));
}
}

105
src/utils/mod.rs Normal file
View File

@@ -0,0 +1,105 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::cmp::Ordering;
use walkdir::WalkDir;
use std::path::Path;
use std::path::PathBuf;
mod platform;
use self::platform::*;
pub fn get_dir_tree(
filenames: &Vec<&str>,
apparent_size: bool,
) -> (bool, HashMap<String, u64>, Vec<String>) {
let mut permissions = 0;
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
let mut data: HashMap<String, u64> = HashMap::new();
let mut top_level_names = Vec::new();
for b in filenames {
let top_level_name = strip_end_slashes(b);
examine_dir(
&Path::new(&top_level_name).to_path_buf(),
apparent_size,
&mut inodes,
&mut data,
&mut permissions,
);
top_level_names.push(top_level_name);
}
(permissions == 0, data, top_level_names)
}
fn strip_end_slashes(s: &str) -> String {
let mut new_name = String::from(s);
while new_name.chars().last() == Some('/') && new_name.len() != 1 {
new_name.pop();
}
new_name
}
fn examine_dir(
top_dir: &PathBuf,
apparent_size: bool,
inodes: &mut HashSet<(u64, u64)>,
data: &mut HashMap<String, u64>,
permissions: &mut u64,
) {
for entry in WalkDir::new(top_dir) {
match entry {
Ok(e) => {
let maybe_size_and_inode = get_metadata(&e, apparent_size);
match maybe_size_and_inode {
Some((size, maybe_inode)) => {
if !apparent_size {
if let Some(inode_dev_pair) = maybe_inode {
if inodes.contains(&inode_dev_pair) {
continue;
}
inodes.insert(inode_dev_pair);
}
}
let mut e_path = e.path().to_path_buf();
loop {
let path_name = e_path.to_string_lossy().to_string();
let s = data.entry(path_name).or_insert(0);
*s += size;
if e_path == *top_dir {
break;
}
e_path.pop();
}
}
None => *permissions += 1,
}
}
_ => {}
}
}
}
pub fn compare_tuple(a :&(String, u64), b: &(String, u64)) -> Ordering {
let result = b.1.cmp(&a.1);
if result == Ordering::Equal {
a.0.cmp(&b.0)
} else {
result
}
}
pub fn sort<'a>(data: HashMap<String, u64>) -> Vec<(String, u64)> {
let mut new_l: Vec<(String, u64)> = data.iter().map(|(a, b)| (a.clone(), *b)).collect();
new_l.sort_by(|a, b| compare_tuple(&a, &b));
new_l
}
pub fn find_big_ones<'a>(new_l: Vec<(String, u64)>, max_to_show: usize) -> Vec<(String, u64)> {
if max_to_show > 0 && new_l.len() > max_to_show {
new_l[0..max_to_show + 1].to_vec()
} else {
new_l
}
}

63
src/utils/platform.rs Normal file
View File

@@ -0,0 +1,63 @@
use walkdir::DirEntry;
fn get_block_size() -> u64 {
// All os specific implementations of MetatdataExt seem to define a block as 512 bytes
// https://doc.rust-lang.org/std/os/linux/fs/trait.MetadataExt.html#tymethod.st_blocks
512
}
#[cfg(target_os = "linux")]
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::linux::fs::MetadataExt;
match d.metadata().ok() {
Some(md) => {
let inode = Some((md.st_ino(), md.st_dev()));
if use_apparent_size {
Some((md.len(), inode))
} else {
Some((md.st_blocks() * get_block_size(), inode))
}
}
None => None,
}
}
#[cfg(target_os = "unix")]
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::unix::fs::MetadataExt;
match d.metadata().ok() {
Some(md) => {
let inode = Some((md.ino(), md.dev()));
if use_apparent_size {
Some((md.len(), inode))
} else {
Some((md.blocks() * get_block_size(), inode))
}
}
None => None,
}
}
#[cfg(target_os = "macos")]
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::macos::fs::MetadataExt;
match d.metadata().ok() {
Some(md) => {
let inode = Some((md.st_ino(), md.st_dev()));
if use_apparent_size {
Some((md.len(), inode))
} else {
Some((md.st_blocks() * get_block_size(), inode))
}
}
None => None,
}
}
#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))]
pub fn get_metadata(d: &DirEntry, _apparent: bool) -> Option<(u64, Option<(u64, u64)>)> {
match d.metadata().ok() {
Some(md) => Some((md.len(), None)),
None => None,
}
}

View File

@@ -1 +0,0 @@
hello

View File

@@ -1 +0,0 @@
hello

View File

@@ -1 +0,0 @@
hello

View File

@@ -1 +0,0 @@
hi

View File

@@ -1,270 +0,0 @@
use assert_cmd::Command;
use std::str;
use std::sync::Once;
static INIT: Once = Once::new();
mod tests_symlinks;
/**
* 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 incase it is there and has incorrect data
let last_slash = dir.rfind('/').unwrap();
let last_part_of_dir = dir.chars().skip(last_slash).collect::<String>();
match Command::new("rm")
.arg("-rf")
.arg("/tmp/".to_owned() + &*last_part_of_dir)
.ok()
{
Ok(_) => {}
Err(_) => {}
};
match Command::new("cp").arg("-r").arg(dir).arg("/tmp/").ok() {
Ok(_) => {}
Err(err) => {
eprintln!("Error copying directory {:?}", err);
}
};
}
pub fn initialize() {
INIT.call_once(|| {
copy_test_data("tests/test_dir");
copy_test_data("tests/test_dir2");
copy_test_data("tests/test_dir_unicode");
});
}
// "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_basic() {
// -c is no color mode - This makes testing much simpler
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd.arg("-c").arg("/tmp/test_dir/").unwrap().stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output()));
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_multi_arg() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd
.arg("-c")
.arg("/tmp/test_dir/many/")
.arg("/tmp/test_dir")
.arg("/tmp/test_dir")
.unwrap()
.stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output()));
}
#[cfg(target_os = "macos")]
fn main_output() -> String {
r#"
0B ┌── a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ many │████████████████████████████████████████████████ │ 100%
4.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "linux")]
fn main_output() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── hello_file│ ░░░░░░░░░░░░░░░░█████████████████ │ 33%
8.0K ┌─┴ many │ █████████████████████████████████ │ 67%
12K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "windows")]
fn main_output() -> String {
"windows results vary by host".to_string()
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_long_paths() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd
.arg("-c")
.arg("-p")
.arg("/tmp/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&main_output_long_paths()));
}
#[cfg(target_os = "macos")]
fn main_output_long_paths() -> String {
r#"
0B ┌── /tmp/test_dir/many/a_file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── /tmp/test_dir/many/hello_file│█████████████████████████████ │ 100%
4.0K ┌─┴ /tmp/test_dir/many │█████████████████████████████ │ 100%
4.0K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "linux")]
fn main_output_long_paths() -> String {
r#"
0B ┌── /tmp/test_dir/many/a_file │ ░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ├── /tmp/test_dir/many/hello_file│ ░░░░░░░░░░██████████ │ 33%
8.0K ┌─┴ /tmp/test_dir/many │ ████████████████████ │ 67%
12K ┌─┴ /tmp/test_dir │█████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "windows")]
fn main_output_long_paths() -> String {
"windows results vary by host".to_string()
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_apparent_size() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let assert = cmd.arg("-c").arg("-s").arg("/tmp/test_dir").unwrap().stdout;
let output = str::from_utf8(&assert).unwrap();
assert!(output.contains(&output_apparent_size()));
}
#[cfg(target_os = "linux")]
fn output_apparent_size() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
4.0K ┌─┴ many │ █████████████████████████ │ 50%
8.0K ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "macos")]
fn output_apparent_size() -> String {
r#"
0B ┌── a_file │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░█ │ 0%
6B ├── hello_file│ ░░░░░░░░░░░░░░░░░░░░░░░░░░██ │ 3%
134B ┌─┴ many │ ████████████████████████████ │ 58%
230B ┌─┴ test_dir │████████████████████████████████████████████████ │ 100%
"#
.trim()
.to_string()
}
#[cfg(target_os = "windows")]
fn output_apparent_size() -> String {
"windows results vary by host".to_string()
}
// Check against directories and files whos names are substrings of each other
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_substring_of_names_and_long_names() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-c").arg("/tmp/test_dir2").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(&no_substring_of_names_output()));
}
#[cfg(target_os = "linux")]
fn no_substring_of_names_output() -> String {
"
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
4.0K ├── dir_name_clash
4.0K │ ┌── hello
8.0K ├─┴ dir
4.0K │ ┌── hello
8.0K ├─┴ dir_substring
24K ┌─┴ test_dir2
"
.trim()
.into()
}
#[cfg(target_os = "macos")]
fn no_substring_of_names_output() -> String {
"
0B ┌── long_dir_name_what_a_very_long_dir_name_what_happens_when_this_g..
4.0K │ ┌── hello
4.0K ├─┴ dir
4.0K ├── dir_name_clash
4.0K │ ┌── hello
4.0K ├─┴ dir_substring
12K ┌─┴ test_dir2
"
.trim()
.into()
}
#[cfg(target_os = "windows")]
fn no_substring_of_names_output() -> String {
"PRs".into()
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_unicode_directories() {
initialize();
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-c").arg("/tmp/test_dir_unicode").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(&unicode_dir()));
}
#[cfg(target_os = "linux")]
fn unicode_dir() -> String {
// The way unicode & asian characters are rendered on the terminal should make this line up
"
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
0B ├── 👩.unicode │ █ │ 0%
4.0K ┌─┴ test_dir_unicode │██████████████████████████████████ │ 100%
"
.trim()
.into()
}
#[cfg(target_os = "macos")]
fn unicode_dir() -> String {
"
0B ┌── ラウトは難しいです!.japan│ █ │ 0%
0B ├── 👩.unicode │ █ │ 0%
0B ┌─┴ test_dir_unicode │ █ │ 0%
"
.trim()
.into()
}
#[cfg(target_os = "windows")]
fn unicode_dir() -> String {
"".into()
}

View File

@@ -1,149 +0,0 @@
use assert_cmd::Command;
use std::str;
/**
* This file contains tests that test a substring of the output using '.contains'
*
* These tests should be the same cross platform
*/
// We can at least test the file names are there
#[test]
pub fn test_basic_output() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("tests/test_dir/").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
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 mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-b").arg("tests/test_dir/").unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
// If bars are not being shown we don't need to pad the output with spaces
assert!(output.contains("many"));
assert!(!output.contains("many "));
}
#[test]
pub fn test_reverse_flag() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-r")
.arg("tests/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
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 mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-d")
.arg("1")
.arg("-s")
.arg("tests/test_dir/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(!output.contains("hello_file"));
}
#[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 mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-d")
.arg("1")
.arg("-f")
.arg("-c")
.arg("tests/test_dir2/")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains("7 ┌─┴ test_dir2"));
}
// Check against directories and files whos names are substrings of each other
#[test]
pub fn test_ignore_dir() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-X")
.arg("dir_substring")
.arg("tests/test_dir2")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(!output.contains("dir_substring"));
}
#[test]
pub fn test_with_bad_param() {
let mut cmd = Command::cargo_bin("dust").unwrap();
let stderr = cmd.arg("-").unwrap().stderr;
let stderr = str::from_utf8(&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 mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("tests/test_dir_hidden_entries")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(".hidden_file"));
assert!(output.contains("┌─┴ test_dir_hidden_entries"));
// Check that adding the '-h' flag causes us to not see hidden files
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-i")
.arg("tests/test_dir_hidden_entries")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
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 mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-c")
.arg("-f")
.arg("tests/test_dir")
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains("1 ┌── a_file "));
assert!(output.contains("1 ├── hello_file"));
assert!(output.contains("3 ┌─┴ many"));
assert!(output.contains("4 ┌─┴ test_dir"));
}

View File

@@ -1 +0,0 @@

View File

@@ -1,139 +0,0 @@
use assert_cmd::Command;
use std::cmp::max;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::str;
use terminal_size::{terminal_size, Height, Width};
use unicode_width::UnicodeWidthStr;
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 get_width_of_terminal() -> u16 {
if let Some((Width(w), Height(_h))) = terminal_size() {
max(w, 80)
} else {
80
}
}
// Mac test runners create tmp files with very long names, hence it may be shortened in the output
fn get_file_name(name: String) -> String {
let terminal_plus_buffer = (get_width_of_terminal() - 14) as usize;
if UnicodeWidthStr::width(&*name) > terminal_plus_buffer {
let trimmed_name = name
.chars()
.take(terminal_plus_buffer - 2)
.collect::<String>();
trimmed_name + ".."
} else {
name
}
}
fn build_temp_file(dir: &TempDir) -> PathBuf {
let file_path = dir.path().join("notes.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "I am a temp file").unwrap();
file_path
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_soft_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let file = build_temp_file(&dir);
let dir_s = dir.path().to_str().unwrap();
let file_path_s = file.to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg("-s")
.arg(file_path_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let c = format!(" ├── {}", get_file_name(link_name_s.into()));
let b = format!(" ┌── {}", get_file_name(file_path_s.into()));
let a = format!("─┴ {}", dir_s);
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-p").arg("-c").arg("-s").arg(dir_s).unwrap().stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(a.as_str()));
assert!(output.contains(b.as_str()));
assert!(output.contains(c.as_str()));
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_hard_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let file = build_temp_file(&dir);
let dir_s = dir.path().to_str().unwrap();
let file_path_s = file.to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg(file_path_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let link_output = format!(" ┌── {}", get_file_name(link_name_s.into()));
let file_output = format!(" ┌── {}", get_file_name(file_path_s.into()));
let dirs_output = format!("─┴ {}", dir_s);
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd.arg("-p").arg("-c").arg(dir_s).unwrap().stdout;
// Because this is a hard link the file and hard link look identical. Therefore
// we cannot guarantee which version will appear first.
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(dirs_output.as_str()));
assert!(output.contains(link_output.as_str()) || output.contains(file_output.as_str()));
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_recursive_sym_link() {
let dir = Builder::new().tempdir().unwrap();
let dir_s = dir.path().to_str().unwrap();
let link_name = dir.path().join("the_link");
let link_name_s = link_name.to_str().unwrap();
let c = Command::new("ln")
.arg("-s")
.arg(dir_s)
.arg(link_name_s)
.output();
assert!(c.is_ok());
let a = format!("─┬ {}", dir_s);
let b = format!(" └── {}", get_file_name(link_name_s.into()));
let mut cmd = Command::cargo_bin("dust").unwrap();
let output = cmd
.arg("-p")
.arg("-c")
.arg("-r")
.arg("-s")
.arg(dir_s)
.unwrap()
.stdout;
let output = str::from_utf8(&output).unwrap();
assert!(output.contains(a.as_str()));
assert!(output.contains(b.as_str()));
}