File dust-1.1.1.obscpio of Package dust

07070100000000000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001300000000dust-1.1.1/.github07070100000001000081A400000000000000000000000166980F00000000BA000000000000000000000000000000000000001600000000dust-1.1.1/.gitignore# Generated by Cargo
# will have compiled files and executables
/target/

# These are backup files generated by rustfmt
**/*.rs.bk
*.swp
.vscode/*
*.idea/*

#ignore macos files
.DS_Store07070100000002000081A400000000000000000000000166980F0000006FC0000000000000000000000000000000000000001600000000dust-1.1.1/Cargo.lock# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
 "memchr",
]

[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"

[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
 "libc",
]

[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
 "winapi",
]

[[package]]
name = "anstream"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
 "anstyle",
 "anstyle-parse",
 "anstyle-query",
 "anstyle-wincon",
 "colorchoice",
 "utf8parse",
]

[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"

[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
 "utf8parse",
]

[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
 "windows-sys 0.52.0",
]

[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
 "anstyle",
 "windows-sys 0.52.0",
]

[[package]]
name = "assert_cmd"
version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8"
dependencies = [
 "anstyle",
 "bstr",
 "doc-comment",
 "predicates",
 "predicates-core",
 "predicates-tree",
 "wait-timeout",
]

[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"

[[package]]
name = "bstr"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
 "memchr",
 "regex-automata",
 "serde",
]

[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"

[[package]]
name = "cc"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"

[[package]]
name = "chrono"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
 "android-tzdata",
 "iana-time-zone",
 "js-sys",
 "num-traits",
 "wasm-bindgen",
 "windows-targets 0.52.4",
]

[[package]]
name = "clap"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
 "clap_builder",
]

[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
 "anstream",
 "anstyle",
 "clap_lex",
 "strsim",
]

[[package]]
name = "clap_complete"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c"
dependencies = [
 "clap",
]

[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"

[[package]]
name = "clap_mangen"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e"
dependencies = [
 "clap",
 "roff",
]

[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"

[[package]]
name = "config-file"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51e72c150781d2c7d4cbcb0b803277caaa80476786994a62961a8f1010dafb"
dependencies = [
 "serde",
 "thiserror",
 "toml",
]

[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"

[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
 "crossbeam-epoch",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"

[[package]]
name = "ctrlc"
version = "3.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345"
dependencies = [
 "nix",
 "windows-sys 0.52.0",
]

[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"

[[package]]
name = "directories"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
dependencies = [
 "dirs-sys",
]

[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
 "libc",
 "redox_users",
 "winapi",
]

[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"

[[package]]
name = "du-dust"
version = "1.1.1"
dependencies = [
 "ansi_term",
 "assert_cmd",
 "chrono",
 "clap",
 "clap_complete",
 "clap_mangen",
 "config-file",
 "ctrlc",
 "directories",
 "filesize",
 "lscolors",
 "rayon",
 "regex",
 "serde",
 "serde_json",
 "stfu8",
 "sysinfo",
 "tempfile",
 "terminal_size",
 "thousands",
 "unicode-width",
 "winapi-util",
]

[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"

[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
 "libc",
 "windows-sys 0.52.0",
]

[[package]]
name = "fastrand"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"

[[package]]
name = "filesize"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12d741e2415d4e2e5bd1c1d00409d1a8865a57892c2d689b504365655d237d43"
dependencies = [
 "winapi",
]

[[package]]
name = "getrandom"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
 "cfg-if",
 "libc",
 "wasi",
]

[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"

[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
 "android_system_properties",
 "core-foundation-sys",
 "iana-time-zone-haiku",
 "js-sys",
 "wasm-bindgen",
 "windows-core",
]

[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
 "cc",
]

[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
 "hermit-abi",
 "libc",
 "windows-sys 0.48.0",
]

[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"

[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
 "wasm-bindgen",
]

[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"

[[package]]
name = "libredox"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
dependencies = [
 "bitflags 2.5.0",
 "libc",
 "redox_syscall",
]

[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"

[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"

[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"

[[package]]
name = "lscolors"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dedc85d67baf5327114fad78ab9418f8893b1121c17d5538dd11005ad1ddf2"
dependencies = [
 "ansi_term",
 "nu-ansi-term",
]

[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"

[[package]]
name = "nix"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
 "bitflags 2.5.0",
 "cfg-if",
 "cfg_aliases",
 "libc",
]

[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
 "winapi",
]

[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
 "overload",
 "winapi",
]

[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
 "autocfg",
]

[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"

[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"

[[package]]
name = "predicates"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8"
dependencies = [
 "anstyle",
 "difflib",
 "predicates-core",
]

[[package]]
name = "predicates-core"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"

[[package]]
name = "predicates-tree"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
dependencies = [
 "predicates-core",
 "termtree",
]

[[package]]
name = "proc-macro2"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
 "either",
 "rayon-core",
]

[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
 "crossbeam-deque",
 "crossbeam-utils",
]

[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
 "bitflags 1.3.2",
]

[[package]]
name = "redox_users"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
dependencies = [
 "getrandom",
 "libredox",
 "thiserror",
]

[[package]]
name = "regex"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-automata",
 "regex-syntax",
]

[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-syntax",
]

[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"

[[package]]
name = "roff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"

[[package]]
name = "rustix"
version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
 "bitflags 1.3.2",
 "errno",
 "io-lifetimes",
 "libc",
 "linux-raw-sys 0.3.8",
 "windows-sys 0.48.0",
]

[[package]]
name = "rustix"
version = "0.38.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
dependencies = [
 "bitflags 2.5.0",
 "errno",
 "libc",
 "linux-raw-sys 0.4.13",
 "windows-sys 0.52.0",
]

[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"

[[package]]
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "serde_json"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [
 "itoa",
 "ryu",
 "serde",
]

[[package]]
name = "stfu8"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978"

[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"

[[package]]
name = "syn"
version = "2.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "sysinfo"
version = "0.27.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33"
dependencies = [
 "cfg-if",
 "core-foundation-sys",
 "libc",
 "ntapi",
 "once_cell",
 "rayon",
 "winapi",
]

[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
 "cfg-if",
 "fastrand",
 "rustix 0.38.32",
 "windows-sys 0.52.0",
]

[[package]]
name = "terminal_size"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
dependencies = [
 "rustix 0.37.27",
 "windows-sys 0.48.0",
]

[[package]]
name = "termtree"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"

[[package]]
name = "thiserror"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "thousands"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"

[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
 "serde",
]

[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"

[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"

[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
 "libc",
]

[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
 "cfg-if",
 "wasm-bindgen-macro",
]

[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
 "bumpalo",
 "log",
 "once_cell",
 "proc-macro2",
 "quote",
 "syn",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
 "quote",
 "wasm-bindgen-macro-support",
]

[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "wasm-bindgen-backend",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"

[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
 "winapi-i686-pc-windows-gnu",
 "winapi-x86_64-pc-windows-gnu",
]

[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"

[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
 "windows-sys 0.52.0",
]

[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
 "windows-targets 0.52.4",
]

[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
 "windows-targets 0.48.5",
]

[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
 "windows-targets 0.52.4",
]

[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
 "windows_aarch64_gnullvm 0.48.5",
 "windows_aarch64_msvc 0.48.5",
 "windows_i686_gnu 0.48.5",
 "windows_i686_msvc 0.48.5",
 "windows_x86_64_gnu 0.48.5",
 "windows_x86_64_gnullvm 0.48.5",
 "windows_x86_64_msvc 0.48.5",
]

[[package]]
name = "windows-targets"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
 "windows_aarch64_gnullvm 0.52.4",
 "windows_aarch64_msvc 0.52.4",
 "windows_i686_gnu 0.52.4",
 "windows_i686_msvc 0.52.4",
 "windows_x86_64_gnu 0.52.4",
 "windows_x86_64_gnullvm 0.52.4",
 "windows_x86_64_msvc 0.52.4",
]

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"

[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"

[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"

[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"

[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"

[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"

[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"

[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"

[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"

[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
07070100000003000081A400000000000000000000000166980F0000000774000000000000000000000000000000000000001600000000dust-1.1.1/Cargo.toml[package]
name = "du-dust"
description = "A more intuitive version of du"
version = "1.1.1"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2021"
readme = "README.md"

documentation = "https://github.com/bootandy/dust"
homepage = "https://github.com/bootandy/dust"
repository = "https://github.com/bootandy/dust"

keywords = ["du", "command-line", "disk", "disk-usage"]
categories = ["command-line-utilities"]
license = "Apache-2.0"

[badges]
travis-ci = { repository = "https://travis-ci.org/bootandy/dust" }

[[bin]]
name = "dust"
path = "src/main.rs"

[profile.release]
codegen-units = 1
lto = true
strip = true

[dependencies]
ansi_term = "0.12"
clap = "4.4"
lscolors = "0.13"
terminal_size = "0.2"
unicode-width = "0.1"
rayon = "1"
thousands = "0.2"
stfu8 = "0.2"
regex = "1"
config-file = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
directories = "4"
sysinfo = "0.27"
ctrlc = "3.4"
chrono = "0.4"

[target.'cfg(windows)'.dependencies]
winapi-util = "0.1"
filesize = "0.2.0"

[dev-dependencies]
assert_cmd = "2"
tempfile = "=3"

[build-dependencies]
clap = "4.4"
clap_complete = "4.4"
clap_mangen = "0.2"

[[test]]
name = "integration"
path = "tests/tests.rs"

[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/dust-v{ version }-{ target }{ archive-suffix }"
bin-dir = "dust-v{ version }-{ target }/{ bin }{ binary-ext }"

[package.metadata.deb]
section = "utils"
assets = [
  [
    "target/release/dust",
    "usr/bin/",
    "755",
  ],
  [
    "LICENSE",
    "usr/share/doc/du-dust/",
    "644",
  ],
  [
    "README.md",
    "usr/share/doc/du-dust/README",
    "644",
  ],
]
extended-description = """\
Dust is meant to give you an instant overview of which directories are using
disk space without requiring sort or head. Dust will print a maximum of one
'Did not have permissions message'.
"""
07070100000004000081A400000000000000000000000166980F0000002C51000000000000000000000000000000000000001300000000dust-1.1.1/LICENSE                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [2023] [andrew boot]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
07070100000005000081A400000000000000000000000166980F00000013A5000000000000000000000000000000000000001500000000dust-1.1.1/README.md[![Build Status](https://github.com/bootandy/dust/actions/workflows/CICD.yml/badge.svg)](https://github.com/bootandy/dust/actions)


# 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)

## Install

#### Cargo <a href="https://repology.org/project/du-dust/versions"><img src="https://repology.org/badge/vertical-allrepos/du-dust.svg" alt="Packaging status" align="right"></a>

- `cargo install du-dust`

#### 🍺 Homebrew (Mac OS)

- `brew install dust`

#### 🍺 Homebrew (Linux)

- `brew install dust`

#### [Pacstall](https://github.com/pacstall/pacstall) (Debian/Ubuntu)

- `pacstall -I dust-bin`

### Anaconda (conda-forge)

- `conda install -c conda-forge dust`

#### [deb-get](https://github.com/wimpysworld/deb-get) (Debian/Ubuntu)

- `deb-get install du-dust`

#### Windows:

- `scoop install dust`
- Windows GNU version - works
- Windows MSVC - requires: [VCRUNTIME140.dll](https://docs.microsoft.com/en-gb/cpp/windows/latest-supported-vc-redist?view=msvc-170)

#### Download

- Download Linux/Mac binary from [Releases](https://github.com/bootandy/dust/releases)
- unzip file: `tar -xvf _downloaded_file.tar.gz`
- move file to executable path: `sudo mv dust /usr/local/bin/`

## Overview

Dust is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust will print a maximum of one 'Did not have permissions message'.

Dust will list a slightly-less-than-the-terminal-height number of the biggest subdirectories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest subdirectories will be colored.

The different colors on the bars: These represent the combined tree hierarchy & disk usage. The shades of grey are used to indicate which parent folder a subfolder belongs to. For instance, look at the above screenshot. `.steam` is a folder taking 44% of the space. From the `.steam` bar is a light grey line that goes up. All these folders are inside `.steam` so if you delete `.steam` all that stuff will be gone too.

## Usage

```
Usage: dust
Usage: dust <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 -D (Show only directories (eg dust -D))
Usage: dust -F (Show only files - finds your largest files)
Usage: dust -r (reverse order of output)
Usage: dust -o si/b/kb/kib/mb/mib/gb/gib (si - prints sizes in powers of 1000. Others print size in that format).
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 -B (--bars-on-right - Percent bars moved to right side of screen)
Usage: dust -i (Do not show hidden files)
Usage: dust -c (No colors [monochrome])
Usage: dust -C (Force colors)
Usage: dust -f (Count files instead of diskspace)
Usage: dust -t (Group by filetype)
Usage: dust -z 10M (min-size, Only include files larger than 10M)
Usage: dust -e regex (Only include files matching this regex (eg dust -e "\.png$" would match png files))
Usage: dust -v regex (Exclude files matching this regex (eg dust -v "\.png$" would ignore png files))
Usage: dust -L (dereference-links - Treat sym links as directories and go into them)
Usage: dust -P (Disable the progress indicator)
Usage: dust -R (For screen readers. Removes bars/symbols. Adds new column: depth level. (May want to use -p for full path too))
Usage: dust -S (Custom Stack size - Use if you see: 'fatal runtime error: stack overflow' (default allocation: low memory=1048576, high memory=1073741824)"),
Usage: dust --skip-total (No total row will be displayed)
Usage: dust -z 40000/30MB/20kib (Exclude output files/directories below size 40000 bytes / 30MB / 20KiB)
Usage: dust -j (Prints JSON representation of directories, try: dust -j  | jq)
Usage: dust --files0-from=FILE (Reads null-terminated file paths from FILE); If FILE is - then read from stdin
```

## Config file

Dust has a config file where the above options can be set.
Either: `~/.config/dust/config.toml` or `~/.dust.toml`
```
$ cat ~/.config/dust/config.toml
reverse=true
```

## 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.
07070100000006000081A400000000000000000000000166980F00000002DD000000000000000000000000000000000000001400000000dust-1.1.1/build.rsuse clap_complete::{generate_to, shells::*};
use clap_mangen::Man;
use std::fs::File;
use std::io::Error;
use std::path::Path;

include!("src/cli.rs");

fn main() -> Result<(), Error> {
    let outdir = "completions";
    let app_name = "dust";
    let mut cmd = build_cli();

    generate_to(Bash, &mut cmd, app_name, outdir)?;
    generate_to(Zsh, &mut cmd, app_name, outdir)?;
    generate_to(Fish, &mut cmd, app_name, outdir)?;
    generate_to(PowerShell, &mut cmd, app_name, outdir)?;
    generate_to(Elvish, &mut cmd, app_name, outdir)?;

    let file = Path::new("man-page").join("dust.1");
    std::fs::create_dir_all("man-page")?;
    let mut file = File::create(file)?;

    Man::new(cmd).render(&mut file)?;

    Ok(())
}
07070100000007000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000000E00000000dust-1.1.1/ci07070100000008000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001700000000dust-1.1.1/completions07070100000009000081A400000000000000000000000166980F0000001933000000000000000000000000000000000000001D00000000dust-1.1.1/completions/_dust#compdef dust

autoload -U is-at-least

_dust() {
    typeset -A opt_args
    typeset -a _arguments_options
    local ret=1

    if is-at-least 5.2; then
        _arguments_options=(-s -S -C)
    else
        _arguments_options=(-s -C)
    fi

    local context curcontext="$curcontext" state line
    _arguments "${_arguments_options[@]}" \
'-d+[Depth to show]:DEPTH: ' \
'--depth=[Depth to show]:DEPTH: ' \
'-T+[Number of threads to use]: : ' \
'--threads=[Number of threads to use]: : ' \
'-n+[Number of lines of output to show. (Default is terminal_height - 10)]:NUMBER: ' \
'--number-of-lines=[Number of lines of output to show. (Default is terminal_height - 10)]:NUMBER: ' \
'*-X+[Exclude any file or directory with this name]:PATH:_files' \
'*--ignore-directory=[Exclude any file or directory with this name]:PATH:_files' \
'-I+[Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter]:FILE:_files' \
'--ignore-all-in-file=[Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter]:FILE:_files' \
'-z+[Minimum size file to include in output]:MIN_SIZE: ' \
'--min-size=[Minimum size file to include in output]:MIN_SIZE: ' \
'(-e --filter -t --file_types)*-v+[Exclude filepaths matching this regex. To ignore png files type\: -v "\\.png\$" ]:REGEX: ' \
'(-e --filter -t --file_types)*--invert-filter=[Exclude filepaths matching this regex. To ignore png files type\: -v "\\.png\$" ]:REGEX: ' \
'(-t --file_types)*-e+[Only include filepaths matching this regex. For png files type\: -e "\\.png\$" ]:REGEX: ' \
'(-t --file_types)*--filter=[Only include filepaths matching this regex. For png files type\: -e "\\.png\$" ]:REGEX: ' \
'-w+[Specify width of output overriding the auto detection of terminal width]:WIDTH: ' \
'--terminal_width=[Specify width of output overriding the auto detection of terminal width]:WIDTH: ' \
'-o+[Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.]:FORMAT:(si b k m g t kb mb gb tb)' \
'--output-format=[Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.]:FORMAT:(si b k m g t kb mb gb tb)' \
'-S+[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]:STACK_SIZE: ' \
'--stack-size=[Specify memory to use as stack size - use if you see\: '\''fatal runtime error\: stack overflow'\'' (default low memory=1048576, high memory=1073741824)]:STACK_SIZE: ' \
'-M+[+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => \[currβˆ’(n+1), currβˆ’n), and -n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)]: : ' \
'--mtime=[+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => \[currβˆ’(n+1), currβˆ’n), and -n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)]: : ' \
'-A+[just like -mtime, but based on file access time]: : ' \
'--atime=[just like -mtime, but based on file access time]: : ' \
'-y+[just like -mtime, but based on file change time]: : ' \
'--ctime=[just like -mtime, but based on file change time]: : ' \
'--files0-from=[run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input]: :_files' \
'-p[Subdirectories will not have their path shortened]' \
'--full-paths[Subdirectories will not have their path shortened]' \
'-L[dereference sym links - Treat sym links as directories and go into them]' \
'--dereference-links[dereference sym links - Treat sym links as directories and go into them]' \
'-x[Only count the files and directories on the same filesystem as the supplied directory]' \
'--limit-filesystem[Only count the files and directories on the same filesystem as the supplied directory]' \
'-s[Use file length instead of blocks]' \
'--apparent-size[Use file length instead of blocks]' \
'-r[Print tree upside down (biggest highest)]' \
'--reverse[Print tree upside down (biggest highest)]' \
'-c[No colors will be printed (Useful for commands like\: watch)]' \
'--no-colors[No colors will be printed (Useful for commands like\: watch)]' \
'-C[Force colors print]' \
'--force-colors[Force colors print]' \
'-b[No percent bars or percentages will be displayed]' \
'--no-percent-bars[No percent bars or percentages will be displayed]' \
'-B[percent bars moved to right side of screen]' \
'--bars-on-right[percent bars moved to right side of screen]' \
'-R[For screen readers. Removes bars. Adds new column\: depth level (May want to use -p too for full path)]' \
'--screen-reader[For screen readers. Removes bars. Adds new column\: depth level (May want to use -p too for full path)]' \
'--skip-total[No total row will be displayed]' \
'-f[Directory '\''size'\'' is number of child files instead of disk size]' \
'--filecount[Directory '\''size'\'' is number of child files instead of disk size]' \
'-i[Do not display hidden files]' \
'--ignore_hidden[Do not display hidden files]' \
'(-d --depth -D --only-dir)-t[show only these file types]' \
'(-d --depth -D --only-dir)--file_types[show only these file types]' \
'-P[Disable the progress indication.]' \
'--no-progress[Disable the progress indication.]' \
'--print-errors[Print path with errors.]' \
'(-F --only-file -t --file_types)-D[Only directories will be displayed.]' \
'(-F --only-file -t --file_types)--only-dir[Only directories will be displayed.]' \
'(-D --only-dir)-F[Only files will be displayed. (Finds your largest files)]' \
'(-D --only-dir)--only-file[Only files will be displayed. (Finds your largest files)]' \
'-j[Output the directory tree as json to the current directory]' \
'--output-json[Output the directory tree as json to the current directory]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
'*::params:_files' \
&& ret=0
}

(( $+functions[_dust_commands] )) ||
_dust_commands() {
    local commands; commands=()
    _describe -t commands 'dust commands' commands "$@"
}

if [ "$funcstack[1]" = "_dust" ]; then
    _dust "$@"
else
    compdef _dust dust
fi
0707010000000A000081A400000000000000000000000166980F0000002F73000000000000000000000000000000000000002100000000dust-1.1.1/completions/_dust.ps1
using namespace System.Management.Automation
using namespace System.Management.Automation.Language

Register-ArgumentCompleter -Native -CommandName 'dust' -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)

    $commandElements = $commandAst.CommandElements
    $command = @(
        'dust'
        for ($i = 1; $i -lt $commandElements.Count; $i++) {
            $element = $commandElements[$i]
            if ($element -isnot [StringConstantExpressionAst] -or
                $element.StringConstantType -ne [StringConstantType]::BareWord -or
                $element.Value.StartsWith('-') -or
                $element.Value -eq $wordToComplete) {
                break
        }
        $element.Value
    }) -join ';'

    $completions = @(switch ($command) {
        'dust' {
            [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Depth to show')
            [CompletionResult]::new('--depth', 'depth', [CompletionResultType]::ParameterName, 'Depth to show')
            [CompletionResult]::new('-T', 'T ', [CompletionResultType]::ParameterName, 'Number of threads to use')
            [CompletionResult]::new('--threads', 'threads', [CompletionResultType]::ParameterName, 'Number of threads to use')
            [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)')
            [CompletionResult]::new('--number-of-lines', 'number-of-lines', [CompletionResultType]::ParameterName, 'Number of lines of output to show. (Default is terminal_height - 10)')
            [CompletionResult]::new('-X', 'X ', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this name')
            [CompletionResult]::new('--ignore-directory', 'ignore-directory', [CompletionResultType]::ParameterName, 'Exclude any file or directory with this name')
            [CompletionResult]::new('-I', 'I ', [CompletionResultType]::ParameterName, 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter')
            [CompletionResult]::new('--ignore-all-in-file', 'ignore-all-in-file', [CompletionResultType]::ParameterName, 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter')
            [CompletionResult]::new('-z', 'z', [CompletionResultType]::ParameterName, 'Minimum size file to include in output')
            [CompletionResult]::new('--min-size', 'min-size', [CompletionResultType]::ParameterName, 'Minimum size file to include in output')
            [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
            [CompletionResult]::new('--invert-filter', 'invert-filter', [CompletionResultType]::ParameterName, 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" ')
            [CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$" ')
            [CompletionResult]::new('--filter', 'filter', [CompletionResultType]::ParameterName, 'Only include filepaths matching this regex. For png files type: -e "\.png$" ')
            [CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
            [CompletionResult]::new('--terminal_width', 'terminal_width', [CompletionResultType]::ParameterName, 'Specify width of output overriding the auto detection of terminal width')
            [CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.')
            [CompletionResult]::new('--output-format', 'output-format', [CompletionResultType]::ParameterName, 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.')
            [CompletionResult]::new('-S', 'S ', [CompletionResultType]::ParameterName, 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)')
            [CompletionResult]::new('--stack-size', 'stack-size', [CompletionResultType]::ParameterName, 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)')
            [CompletionResult]::new('-M', 'M ', [CompletionResultType]::ParameterName, '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => [currβˆ’(n+1), currβˆ’n), and -n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)')
            [CompletionResult]::new('--mtime', 'mtime', [CompletionResultType]::ParameterName, '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => [currβˆ’(n+1), currβˆ’n), and -n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)')
            [CompletionResult]::new('-A', 'A ', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file access time')
            [CompletionResult]::new('--atime', 'atime', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file access time')
            [CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file change time')
            [CompletionResult]::new('--ctime', 'ctime', [CompletionResultType]::ParameterName, 'just like -mtime, but based on file change time')
            [CompletionResult]::new('--files0-from', 'files0-from', [CompletionResultType]::ParameterName, 'run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input')
            [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened')
            [CompletionResult]::new('--full-paths', 'full-paths', [CompletionResultType]::ParameterName, 'Subdirectories will not have their path shortened')
            [CompletionResult]::new('-L', 'L ', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them')
            [CompletionResult]::new('--dereference-links', 'dereference-links', [CompletionResultType]::ParameterName, 'dereference sym links - Treat sym links as directories and go into them')
            [CompletionResult]::new('-x', 'x', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory')
            [CompletionResult]::new('--limit-filesystem', 'limit-filesystem', [CompletionResultType]::ParameterName, 'Only count the files and directories on the same filesystem as the supplied directory')
            [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Use file length instead of blocks')
            [CompletionResult]::new('--apparent-size', 'apparent-size', [CompletionResultType]::ParameterName, 'Use file length instead of blocks')
            [CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)')
            [CompletionResult]::new('--reverse', 'reverse', [CompletionResultType]::ParameterName, 'Print tree upside down (biggest highest)')
            [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
            [CompletionResult]::new('--no-colors', 'no-colors', [CompletionResultType]::ParameterName, 'No colors will be printed (Useful for commands like: watch)')
            [CompletionResult]::new('-C', 'C ', [CompletionResultType]::ParameterName, 'Force colors print')
            [CompletionResult]::new('--force-colors', 'force-colors', [CompletionResultType]::ParameterName, 'Force colors print')
            [CompletionResult]::new('-b', 'b', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed')
            [CompletionResult]::new('--no-percent-bars', 'no-percent-bars', [CompletionResultType]::ParameterName, 'No percent bars or percentages will be displayed')
            [CompletionResult]::new('-B', 'B ', [CompletionResultType]::ParameterName, 'percent bars moved to right side of screen')
            [CompletionResult]::new('--bars-on-right', 'bars-on-right', [CompletionResultType]::ParameterName, 'percent bars moved to right side of screen')
            [CompletionResult]::new('-R', 'R ', [CompletionResultType]::ParameterName, 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)')
            [CompletionResult]::new('--screen-reader', 'screen-reader', [CompletionResultType]::ParameterName, 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)')
            [CompletionResult]::new('--skip-total', 'skip-total', [CompletionResultType]::ParameterName, 'No total row will be displayed')
            [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files instead of disk size')
            [CompletionResult]::new('--filecount', 'filecount', [CompletionResultType]::ParameterName, 'Directory ''size'' is number of child files instead of disk size')
            [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Do not display hidden files')
            [CompletionResult]::new('--ignore_hidden', 'ignore_hidden', [CompletionResultType]::ParameterName, 'Do not display hidden files')
            [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'show only these file types')
            [CompletionResult]::new('--file_types', 'file_types', [CompletionResultType]::ParameterName, 'show only these file types')
            [CompletionResult]::new('-P', 'P ', [CompletionResultType]::ParameterName, 'Disable the progress indication.')
            [CompletionResult]::new('--no-progress', 'no-progress', [CompletionResultType]::ParameterName, 'Disable the progress indication.')
            [CompletionResult]::new('--print-errors', 'print-errors', [CompletionResultType]::ParameterName, 'Print path with errors.')
            [CompletionResult]::new('-D', 'D ', [CompletionResultType]::ParameterName, 'Only directories will be displayed.')
            [CompletionResult]::new('--only-dir', 'only-dir', [CompletionResultType]::ParameterName, 'Only directories will be displayed.')
            [CompletionResult]::new('-F', 'F ', [CompletionResultType]::ParameterName, 'Only files will be displayed. (Finds your largest files)')
            [CompletionResult]::new('--only-file', 'only-file', [CompletionResultType]::ParameterName, 'Only files will be displayed. (Finds your largest files)')
            [CompletionResult]::new('-j', 'j', [CompletionResultType]::ParameterName, 'Output the directory tree as json to the current directory')
            [CompletionResult]::new('--output-json', 'output-json', [CompletionResultType]::ParameterName, 'Output the directory tree as json to the current directory')
            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
            [CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
            break
        }
    })

    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
        Sort-Object -Property ListItemText
}
0707010000000B000081A400000000000000000000000166980F00000018B7000000000000000000000000000000000000002100000000dust-1.1.1/completions/dust.bash_dust() {
    local i cur prev opts cmd
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    cmd=""
    opts=""

    for i in ${COMP_WORDS[@]}
    do
        case "${cmd},${i}" in
            ",$1")
                cmd="dust"
                ;;
            *)
                ;;
        esac
    done

    case "${cmd}" in
        dust)
            opts="-d -T -n -p -X -I -L -x -s -r -c -C -b -B -z -R -f -i -v -e -t -w -P -D -F -o -S -j -M -A -y -h -V --depth --threads --number-of-lines --full-paths --ignore-directory --ignore-all-in-file --dereference-links --limit-filesystem --apparent-size --reverse --no-colors --force-colors --no-percent-bars --bars-on-right --min-size --screen-reader --skip-total --filecount --ignore_hidden --invert-filter --filter --file_types --terminal_width --no-progress --print-errors --only-dir --only-file --output-format --stack-size --output-json --mtime --atime --ctime --files0-from --help --version [PATH]..."
            if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                return 0
            fi
            case "${prev}" in
                --depth)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -d)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --threads)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -T)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --number-of-lines)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -n)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --ignore-directory)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -X)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --ignore-all-in-file)
                    local oldifs
                    if [[ -v IFS ]]; then
                        oldifs="$IFS"
                    fi
                    IFS=$'\n'
                    COMPREPLY=($(compgen -f "${cur}"))
                    if [[ -v oldifs ]]; then
                        IFS="$oldifs"
                    fi
                    if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
                        compopt -o filenames
                    fi
                    return 0
                    ;;
                -I)
                    local oldifs
                    if [[ -v IFS ]]; then
                        oldifs="$IFS"
                    fi
                    IFS=$'\n'
                    COMPREPLY=($(compgen -f "${cur}"))
                    if [[ -v oldifs ]]; then
                        IFS="$oldifs"
                    fi
                    if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
                        compopt -o filenames
                    fi
                    return 0
                    ;;
                --min-size)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -z)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --invert-filter)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -v)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --filter)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -e)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --terminal_width)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -w)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --output-format)
                    COMPREPLY=($(compgen -W "si b k m g t kb mb gb tb" -- "${cur}"))
                    return 0
                    ;;
                -o)
                    COMPREPLY=($(compgen -W "si b k m g t kb mb gb tb" -- "${cur}"))
                    return 0
                    ;;
                --stack-size)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -S)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --mtime)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -M)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --atime)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -A)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --ctime)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                -y)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                --files0-from)
                    COMPREPLY=($(compgen -f "${cur}"))
                    return 0
                    ;;
                *)
                    COMPREPLY=()
                    ;;
            esac
            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
            return 0
            ;;
    esac
}

if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
    complete -F _dust -o nosort -o bashdefault -o default dust
else
    complete -F _dust -o bashdefault -o default dust
fi
0707010000000C000081A400000000000000000000000166980F0000001A4F000000000000000000000000000000000000002000000000dust-1.1.1/completions/dust.elv
use builtin;
use str;

set edit:completion:arg-completer[dust] = {|@words|
    fn spaces {|n|
        builtin:repeat $n ' ' | str:join ''
    }
    fn cand {|text desc|
        edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
    }
    var command = 'dust'
    for word $words[1..-1] {
        if (str:has-prefix $word '-') {
            break
        }
        set command = $command';'$word
    }
    var completions = [
        &'dust'= {
            cand -d 'Depth to show'
            cand --depth 'Depth to show'
            cand -T 'Number of threads to use'
            cand --threads 'Number of threads to use'
            cand -n 'Number of lines of output to show. (Default is terminal_height - 10)'
            cand --number-of-lines 'Number of lines of output to show. (Default is terminal_height - 10)'
            cand -X 'Exclude any file or directory with this name'
            cand --ignore-directory 'Exclude any file or directory with this name'
            cand -I 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter'
            cand --ignore-all-in-file 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter'
            cand -z 'Minimum size file to include in output'
            cand --min-size 'Minimum size file to include in output'
            cand -v 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" '
            cand --invert-filter 'Exclude filepaths matching this regex. To ignore png files type: -v "\.png$" '
            cand -e 'Only include filepaths matching this regex. For png files type: -e "\.png$" '
            cand --filter 'Only include filepaths matching this regex. For png files type: -e "\.png$" '
            cand -w 'Specify width of output overriding the auto detection of terminal width'
            cand --terminal_width 'Specify width of output overriding the auto detection of terminal width'
            cand -o 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.'
            cand --output-format 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.'
            cand -S 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)'
            cand --stack-size 'Specify memory to use as stack size - use if you see: ''fatal runtime error: stack overflow'' (default low memory=1048576, high memory=1073741824)'
            cand -M '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => [currβˆ’(n+1), currβˆ’n), and -n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)'
            cand --mtime '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => [currβˆ’(n+1), currβˆ’n), and -n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)'
            cand -A 'just like -mtime, but based on file access time'
            cand --atime 'just like -mtime, but based on file access time'
            cand -y 'just like -mtime, but based on file change time'
            cand --ctime 'just like -mtime, but based on file change time'
            cand --files0-from 'run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input'
            cand -p 'Subdirectories will not have their path shortened'
            cand --full-paths 'Subdirectories will not have their path shortened'
            cand -L 'dereference sym links - Treat sym links as directories and go into them'
            cand --dereference-links 'dereference sym links - Treat sym links as directories and go into them'
            cand -x 'Only count the files and directories on the same filesystem as the supplied directory'
            cand --limit-filesystem 'Only count the files and directories on the same filesystem as the supplied directory'
            cand -s 'Use file length instead of blocks'
            cand --apparent-size 'Use file length instead of blocks'
            cand -r 'Print tree upside down (biggest highest)'
            cand --reverse 'Print tree upside down (biggest highest)'
            cand -c 'No colors will be printed (Useful for commands like: watch)'
            cand --no-colors 'No colors will be printed (Useful for commands like: watch)'
            cand -C 'Force colors print'
            cand --force-colors 'Force colors print'
            cand -b 'No percent bars or percentages will be displayed'
            cand --no-percent-bars 'No percent bars or percentages will be displayed'
            cand -B 'percent bars moved to right side of screen'
            cand --bars-on-right 'percent bars moved to right side of screen'
            cand -R 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)'
            cand --screen-reader 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)'
            cand --skip-total 'No total row will be displayed'
            cand -f 'Directory ''size'' is number of child files instead of disk size'
            cand --filecount 'Directory ''size'' is number of child files instead of disk size'
            cand -i 'Do not display hidden files'
            cand --ignore_hidden 'Do not display hidden files'
            cand -t 'show only these file types'
            cand --file_types 'show only these file types'
            cand -P 'Disable the progress indication.'
            cand --no-progress 'Disable the progress indication.'
            cand --print-errors 'Print path with errors.'
            cand -D 'Only directories will be displayed.'
            cand --only-dir 'Only directories will be displayed.'
            cand -F 'Only files will be displayed. (Finds your largest files)'
            cand --only-file 'Only files will be displayed. (Finds your largest files)'
            cand -j 'Output the directory tree as json to the current directory'
            cand --output-json 'Output the directory tree as json to the current directory'
            cand -h 'Print help'
            cand --help 'Print help'
            cand -V 'Print version'
            cand --version 'Print version'
        }
    ]
    $completions[$command]
}
0707010000000D000081A400000000000000000000000166980F0000000F02000000000000000000000000000000000000002100000000dust-1.1.1/completions/dust.fishcomplete -c dust -s d -l depth -d 'Depth to show' -r
complete -c dust -s T -l threads -d 'Number of threads to use' -r
complete -c dust -s n -l number-of-lines -d 'Number of lines of output to show. (Default is terminal_height - 10)' -r
complete -c dust -s X -l ignore-directory -d 'Exclude any file or directory with this name' -r -F
complete -c dust -s I -l ignore-all-in-file -d 'Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter' -r -F
complete -c dust -s z -l min-size -d 'Minimum size file to include in output' -r
complete -c dust -s v -l invert-filter -d 'Exclude filepaths matching this regex. To ignore png files type: -v "\\.png$" ' -r
complete -c dust -s e -l filter -d 'Only include filepaths matching this regex. For png files type: -e "\\.png$" ' -r
complete -c dust -s w -l terminal_width -d 'Specify width of output overriding the auto detection of terminal width' -r
complete -c dust -s o -l output-format -d 'Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.' -r -f -a "{si	'',b	'',k	'',m	'',g	'',t	'',kb	'',mb	'',gb	'',tb	''}"
complete -c dust -s S -l stack-size -d 'Specify memory to use as stack size - use if you see: \'fatal runtime error: stack overflow\' (default low memory=1048576, high memory=1073741824)' -r
complete -c dust -s M -l mtime -d '+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => [currβˆ’(n+1), currβˆ’n), and -n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)' -r
complete -c dust -s A -l atime -d 'just like -mtime, but based on file access time' -r
complete -c dust -s y -l ctime -d 'just like -mtime, but based on file change time' -r
complete -c dust -l files0-from -d 'run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input' -r -F
complete -c dust -s p -l full-paths -d 'Subdirectories will not have their path shortened'
complete -c dust -s L -l dereference-links -d 'dereference sym links - Treat sym links as directories and go into them'
complete -c dust -s x -l limit-filesystem -d 'Only count the files and directories on the same filesystem as the supplied directory'
complete -c dust -s s -l apparent-size -d 'Use file length instead of blocks'
complete -c dust -s r -l reverse -d 'Print tree upside down (biggest highest)'
complete -c dust -s c -l no-colors -d 'No colors will be printed (Useful for commands like: watch)'
complete -c dust -s C -l force-colors -d 'Force colors print'
complete -c dust -s b -l no-percent-bars -d 'No percent bars or percentages will be displayed'
complete -c dust -s B -l bars-on-right -d 'percent bars moved to right side of screen'
complete -c dust -s R -l screen-reader -d 'For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)'
complete -c dust -l skip-total -d 'No total row will be displayed'
complete -c dust -s f -l filecount -d 'Directory \'size\' is number of child files instead of disk size'
complete -c dust -s i -l ignore_hidden -d 'Do not display hidden files'
complete -c dust -s t -l file_types -d 'show only these file types'
complete -c dust -s P -l no-progress -d 'Disable the progress indication.'
complete -c dust -l print-errors -d 'Print path with errors.'
complete -c dust -s D -l only-dir -d 'Only directories will be displayed.'
complete -c dust -s F -l only-file -d 'Only files will be displayed. (Finds your largest files)'
complete -c dust -s j -l output-json -d 'Output the directory tree as json to the current directory'
complete -c dust -s h -l help -d 'Print help'
complete -c dust -s V -l version -d 'Print version'
0707010000000E000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001200000000dust-1.1.1/config0707010000000F000081A400000000000000000000000166980F0000000240000000000000000000000000000000000000001E00000000dust-1.1.1/config/config.toml# Sample Config file, works with toml and yaml
# Place in either:
#   ~/.config/dust/config.toml
#   ~/.dust.toml

# Print tree upside down (biggest highest)
reverse=true

# Subdirectories will not have their path shortened
display-full-paths=true

# Use file length instead of blocks
display-apparent-size=true

# No colors will be printed
no-colors=true

# No percent bars or percentages will be displayed
no-bars=true

# No total row will be displayed
skip-total=true

# Do not display hidden files
ignore-hidden=true

# print sizes in powers of 1000 (e.g., 1.1G)
iso=true
07070100000010000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001400000000dust-1.1.1/man-page07070100000011000081A400000000000000000000000166980F0000001460000000000000000000000000000000000000001B00000000dust-1.1.1/man-page/dust.1.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH Dust 1  "Dust 1.1.1" 
.SH NAME
Dust \- Like du but more intuitive
.SH SYNOPSIS
\fBdust\fR [\fB\-d\fR|\fB\-\-depth\fR] [\fB\-T\fR|\fB\-\-threads\fR] [\fB\-n\fR|\fB\-\-number\-of\-lines\fR] [\fB\-p\fR|\fB\-\-full\-paths\fR] [\fB\-X\fR|\fB\-\-ignore\-directory\fR] [\fB\-I\fR|\fB\-\-ignore\-all\-in\-file\fR] [\fB\-L\fR|\fB\-\-dereference\-links\fR] [\fB\-x\fR|\fB\-\-limit\-filesystem\fR] [\fB\-s\fR|\fB\-\-apparent\-size\fR] [\fB\-r\fR|\fB\-\-reverse\fR] [\fB\-c\fR|\fB\-\-no\-colors\fR] [\fB\-C\fR|\fB\-\-force\-colors\fR] [\fB\-b\fR|\fB\-\-no\-percent\-bars\fR] [\fB\-B\fR|\fB\-\-bars\-on\-right\fR] [\fB\-z\fR|\fB\-\-min\-size\fR] [\fB\-R\fR|\fB\-\-screen\-reader\fR] [\fB\-\-skip\-total\fR] [\fB\-f\fR|\fB\-\-filecount\fR] [\fB\-i\fR|\fB\-\-ignore_hidden\fR] [\fB\-v\fR|\fB\-\-invert\-filter\fR] [\fB\-e\fR|\fB\-\-filter\fR] [\fB\-t\fR|\fB\-\-file_types\fR] [\fB\-w\fR|\fB\-\-terminal_width\fR] [\fB\-P\fR|\fB\-\-no\-progress\fR] [\fB\-\-print\-errors\fR] [\fB\-D\fR|\fB\-\-only\-dir\fR] [\fB\-F\fR|\fB\-\-only\-file\fR] [\fB\-o\fR|\fB\-\-output\-format\fR] [\fB\-S\fR|\fB\-\-stack\-size\fR] [\fB\-j\fR|\fB\-\-output\-json\fR] [\fB\-M\fR|\fB\-\-mtime\fR] [\fB\-A\fR|\fB\-\-atime\fR] [\fB\-y\fR|\fB\-\-ctime\fR] [\fB\-\-files0\-from\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIPATH\fR] 
.SH DESCRIPTION
Like du but more intuitive
.SH OPTIONS
.TP
\fB\-d\fR, \fB\-\-depth\fR=\fIDEPTH\fR
Depth to show
.TP
\fB\-T\fR, \fB\-\-threads\fR
Number of threads to use
.TP
\fB\-n\fR, \fB\-\-number\-of\-lines\fR=\fINUMBER\fR
Number of lines of output to show. (Default is terminal_height \- 10)
.TP
\fB\-p\fR, \fB\-\-full\-paths\fR
Subdirectories will not have their path shortened
.TP
\fB\-X\fR, \fB\-\-ignore\-directory\fR=\fIPATH\fR
Exclude any file or directory with this name
.TP
\fB\-I\fR, \fB\-\-ignore\-all\-in\-file\fR=\fIFILE\fR
Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by \-\-invert_filter
.TP
\fB\-L\fR, \fB\-\-dereference\-links\fR
dereference sym links \- Treat sym links as directories and go into them
.TP
\fB\-x\fR, \fB\-\-limit\-filesystem\fR
Only count the files and directories on the same filesystem as the supplied directory
.TP
\fB\-s\fR, \fB\-\-apparent\-size\fR
Use file length instead of blocks
.TP
\fB\-r\fR, \fB\-\-reverse\fR
Print tree upside down (biggest highest)
.TP
\fB\-c\fR, \fB\-\-no\-colors\fR
No colors will be printed (Useful for commands like: watch)
.TP
\fB\-C\fR, \fB\-\-force\-colors\fR
Force colors print
.TP
\fB\-b\fR, \fB\-\-no\-percent\-bars\fR
No percent bars or percentages will be displayed
.TP
\fB\-B\fR, \fB\-\-bars\-on\-right\fR
percent bars moved to right side of screen
.TP
\fB\-z\fR, \fB\-\-min\-size\fR=\fIMIN_SIZE\fR
Minimum size file to include in output
.TP
\fB\-R\fR, \fB\-\-screen\-reader\fR
For screen readers. Removes bars. Adds new column: depth level (May want to use \-p too for full path)
.TP
\fB\-\-skip\-total\fR
No total row will be displayed
.TP
\fB\-f\fR, \fB\-\-filecount\fR
Directory \*(Aqsize\*(Aq is number of child files instead of disk size
.TP
\fB\-i\fR, \fB\-\-ignore_hidden\fR
Do not display hidden files
.TP
\fB\-v\fR, \fB\-\-invert\-filter\fR=\fIREGEX\fR
Exclude filepaths matching this regex. To ignore png files type: \-v "\\.png$" 
.TP
\fB\-e\fR, \fB\-\-filter\fR=\fIREGEX\fR
Only include filepaths matching this regex. For png files type: \-e "\\.png$" 
.TP
\fB\-t\fR, \fB\-\-file_types\fR
show only these file types
.TP
\fB\-w\fR, \fB\-\-terminal_width\fR=\fIWIDTH\fR
Specify width of output overriding the auto detection of terminal width
.TP
\fB\-P\fR, \fB\-\-no\-progress\fR
Disable the progress indication.
.TP
\fB\-\-print\-errors\fR
Print path with errors.
.TP
\fB\-D\fR, \fB\-\-only\-dir\fR
Only directories will be displayed.
.TP
\fB\-F\fR, \fB\-\-only\-file\fR
Only files will be displayed. (Finds your largest files)
.TP
\fB\-o\fR, \fB\-\-output\-format\fR=\fIFORMAT\fR
Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.
.br

.br
[\fIpossible values: \fRsi, b, k, m, g, t, kb, mb, gb, tb]
.TP
\fB\-S\fR, \fB\-\-stack\-size\fR=\fISTACK_SIZE\fR
Specify memory to use as stack size \- use if you see: \*(Aqfatal runtime error: stack overflow\*(Aq (default low memory=1048576, high memory=1073741824)
.TP
\fB\-j\fR, \fB\-\-output\-json\fR
Output the directory tree as json to the current directory
.TP
\fB\-M\fR, \fB\-\-mtime\fR
+/\-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => [currβˆ’(n+1), currβˆ’n), and \-n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)
.TP
\fB\-A\fR, \fB\-\-atime\fR
just like \-mtime, but based on file access time
.TP
\fB\-y\fR, \fB\-\-ctime\fR
just like \-mtime, but based on file change time
.TP
\fB\-\-files0\-from\fR
run dust on NUL\-terminated file names specified in file; if argument is \-, then read names from standard input
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
\fB\-V\fR, \fB\-\-version\fR
Print version
.TP
[\fIPATH\fR]

.SH VERSION
v1.1.1
07070100000012000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001100000000dust-1.1.1/media07070100000013000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000000F00000000dust-1.1.1/src07070100000014000081A400000000000000000000000166980F0000002C0B000000000000000000000000000000000000001600000000dust-1.1.1/src/cli.rsuse clap::{builder::PossibleValue, value_parser, Arg, Command};

// For single thread mode set this variable on your command line:
// export RAYON_NUM_THREADS=1

pub fn build_cli() -> Command {
    Command::new("Dust")
        .about("Like du but more intuitive")
        .version(env!("CARGO_PKG_VERSION"))
        .arg(
            Arg::new("depth")
                .short('d')
                .long("depth")
                .value_name("DEPTH")
                .value_parser(value_parser!(usize))
                .help("Depth to show")
                .num_args(1)
        )
        .arg(
            Arg::new("threads")
                .short('T')
                .long("threads")
                .value_parser(value_parser!(usize))
                .help("Number of threads to use")
                .num_args(1)
        )
        .arg(
            Arg::new("number_of_lines")
                .short('n')
                .long("number-of-lines")
                .value_name("NUMBER")
                .value_parser(value_parser!(usize))
                .help("Number of lines of output to show. (Default is terminal_height - 10)")
                .num_args(1)
        )
        .arg(
            Arg::new("display_full_paths")
                .short('p')
                .long("full-paths")
                .action(clap::ArgAction::SetTrue)
                .help("Subdirectories will not have their path shortened"),
        )
        .arg(
            Arg::new("ignore_directory")
                .short('X')
                .long("ignore-directory")
                .value_name("PATH")
                .value_hint(clap::ValueHint::AnyPath)
                .action(clap::ArgAction::Append)
                .help("Exclude any file or directory with this name"),
        )
        .arg(
            Arg::new("ignore_all_in_file")
                .short('I')
                .long("ignore-all-in-file")
                .value_name("FILE")
                .value_hint(clap::ValueHint::FilePath)
                .value_parser(value_parser!(String))
                .help("Exclude any file or directory with a regex matching that listed in this file, the file entries will be added to the ignore regexs provided by --invert_filter"),
        )
         .arg(
            Arg::new("dereference_links")
                .short('L')
                .long("dereference-links")
                .action(clap::ArgAction::SetTrue)
                .help("dereference sym links - Treat sym links as directories and go into them"),
        )
        .arg(
            Arg::new("limit_filesystem")
                .short('x')
                .long("limit-filesystem")
                .action(clap::ArgAction::SetTrue)
                .help("Only count the files and directories on the same filesystem as the supplied directory"),
        )
        .arg(
            Arg::new("display_apparent_size")
                .short('s')
                .long("apparent-size")
                .action(clap::ArgAction::SetTrue)
                .help("Use file length instead of blocks"),
        )
        .arg(
            Arg::new("reverse")
                .short('r')
                .long("reverse")
                .action(clap::ArgAction::SetTrue)
                .help("Print tree upside down (biggest highest)"),
        )
        .arg(
            Arg::new("no_colors")
                .short('c')
                .long("no-colors")
                .action(clap::ArgAction::SetTrue)
                .help("No colors will be printed (Useful for commands like: watch)"),
        )
        .arg(
            Arg::new("force_colors")
                .short('C')
                .long("force-colors")
                .action(clap::ArgAction::SetTrue)
                .help("Force colors print"),
        )
        .arg(
            Arg::new("no_bars")
                .short('b')
                .long("no-percent-bars")
                .action(clap::ArgAction::SetTrue)
                .help("No percent bars or percentages will be displayed"),
        )
        .arg(
            Arg::new("bars_on_right")
                .short('B')
                .long("bars-on-right")
                .action(clap::ArgAction::SetTrue)
                .help("percent bars moved to right side of screen"),
        )
        .arg(
            Arg::new("min_size")
                .short('z')
                .long("min-size")
                .value_name("MIN_SIZE")
                .num_args(1)
                .help("Minimum size file to include in output"),
        )
        .arg(
            Arg::new("screen_reader")
                .short('R')
                .long("screen-reader")
                .action(clap::ArgAction::SetTrue)
                .help("For screen readers. Removes bars. Adds new column: depth level (May want to use -p too for full path)"),
        )
        .arg(
            Arg::new("skip_total")
                .long("skip-total")
                .action(clap::ArgAction::SetTrue)
                .help("No total row will be displayed"),
        )
        .arg(
            Arg::new("by_filecount")
                .short('f')
                .long("filecount")
                .action(clap::ArgAction::SetTrue)
                .help("Directory 'size' is number of child files instead of disk size"),
        )
        .arg(
            Arg::new("ignore_hidden")
                .short('i') // Do not use 'h' this is used by 'help'
                .long("ignore_hidden")
                .action(clap::ArgAction::SetTrue)
                .help("Do not display hidden files"),
        )
        .arg(
            Arg::new("invert_filter")
                .short('v')
                .long("invert-filter")
                .value_name("REGEX")
                .action(clap::ArgAction::Append)
                .conflicts_with("filter")
                .conflicts_with("types")
                .help("Exclude filepaths matching this regex. To ignore png files type: -v \"\\.png$\" "),
        )
        .arg(
            Arg::new("filter")
                .short('e')
                .long("filter")
                .value_name("REGEX")
                .action(clap::ArgAction::Append)
                .conflicts_with("types")
                .help("Only include filepaths matching this regex. For png files type: -e \"\\.png$\" "),
        )
        .arg(
            Arg::new("types")
                .short('t')
                .long("file_types")
                .conflicts_with("depth")
                .conflicts_with("only_dir")
                .action(clap::ArgAction::SetTrue)
                .help("show only these file types"),
        )
        .arg(
            Arg::new("width")
                .short('w')
                .long("terminal_width")
                .value_name("WIDTH")
                .value_parser(value_parser!(usize))
                .num_args(1)
                .help("Specify width of output overriding the auto detection of terminal width"),
        )
        .arg(
            Arg::new("disable_progress")
                .short('P')
                .long("no-progress")
                .action(clap::ArgAction::SetTrue)
                .help("Disable the progress indication."),
        )
        .arg(
            Arg::new("print_errors")
                .long("print-errors")
                .action(clap::ArgAction::SetTrue)
                .help("Print path with errors."),
        )
        .arg(
            Arg::new("only_dir")
                .short('D')
                .long("only-dir")
                .conflicts_with("only_file")
                .conflicts_with("types")
                .action(clap::ArgAction::SetTrue)
                .help("Only directories will be displayed."),
        )
        .arg(
            Arg::new("only_file")
                .short('F')
                .long("only-file")
                .conflicts_with("only_dir")
                .action(clap::ArgAction::SetTrue)
                .help("Only files will be displayed. (Finds your largest files)"),
        )
        .arg(
            Arg::new("output_format")
                .short('o')
                .long("output-format")
                .value_name("FORMAT")
                .value_parser([
                    PossibleValue::new("si"),
                    PossibleValue::new("b"),
                    PossibleValue::new("k").alias("kib"),
                    PossibleValue::new("m").alias("mib"),
                    PossibleValue::new("g").alias("gib"),
                    PossibleValue::new("t").alias("tib"),
                    PossibleValue::new("kb"),
                    PossibleValue::new("mb"),
                    PossibleValue::new("gb"),
                    PossibleValue::new("tb"),
                ])
                .ignore_case(true)
                .help("Changes output display size. si will print sizes in powers of 1000. b k m g t kb mb gb tb will print the whole tree in that size.")
        )
        .arg(
            Arg::new("stack_size")
                .short('S')
                .long("stack-size")
                .value_name("STACK_SIZE")
                .value_parser(value_parser!(usize))
                .num_args(1)
                .help("Specify memory to use as stack size - use if you see: 'fatal runtime error: stack overflow' (default low memory=1048576, high memory=1073741824)"),
        )
        .arg(
            Arg::new("params")
                .value_name("PATH")
                .value_hint(clap::ValueHint::AnyPath)
                .value_parser(value_parser!(String))
                .num_args(1..)
        )
        .arg(
            Arg::new("output_json")
                .short('j')
                .long("output-json")
                .action(clap::ArgAction::SetTrue)
                .help("Output the directory tree as json to the current directory"),
        )
        .arg(
            Arg::new("mtime")
            .short('M')
            .long("mtime")
            .num_args(1)
            .allow_hyphen_values(true)
            .value_parser(value_parser!(String))
            .help("+/-n matches files modified more/less than n days ago , and n matches files modified exactly n days ago, days are rounded down.That is +n => (βˆ’βˆž, currβˆ’(n+1)), n => [currβˆ’(n+1), currβˆ’n), and -n => (π‘π‘’π‘Ÿπ‘Ÿβˆ’π‘›, +∞)")
        )
        .arg(
            Arg::new("atime")
            .short('A')
            .long("atime")
            .num_args(1)
            .allow_hyphen_values(true)
            .value_parser(value_parser!(String))
            .help("just like -mtime, but based on file access time")
        )
        .arg(
            Arg::new("ctime")
            .short('y')
            .long("ctime")
            .num_args(1)
            .allow_hyphen_values(true)
            .value_parser(value_parser!(String))
            .help("just like -mtime, but based on file change time")
        )
        .arg(
            Arg::new("files0_from")
                .long("files0-from")
                .value_hint(clap::ValueHint::AnyPath)
                .value_parser(value_parser!(String))
                .num_args(1)
                .help("run dust on NUL-terminated file names specified in file; if argument is -, then read names from standard input"),
        )
}
07070100000015000081A400000000000000000000000166980F0000002DF8000000000000000000000000000000000000001900000000dust-1.1.1/src/config.rsuse chrono::{Local, TimeZone};
use clap::ArgMatches;
use config_file::FromConfigFile;
use regex::Regex;
use serde::Deserialize;
use std::io::IsTerminal;
use std::path::Path;
use std::path::PathBuf;

use crate::dir_walker::Operater;
use crate::display::get_number_format;

pub static DAY_SECONDS: i64 = 24 * 60 * 60;

#[derive(Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
#[serde(deny_unknown_fields)]
pub struct Config {
    pub display_full_paths: Option<bool>,
    pub display_apparent_size: Option<bool>,
    pub reverse: Option<bool>,
    pub no_colors: Option<bool>,
    pub force_colors: Option<bool>,
    pub no_bars: Option<bool>,
    pub skip_total: Option<bool>,
    pub screen_reader: Option<bool>,
    pub ignore_hidden: Option<bool>,
    pub output_format: Option<String>,
    pub min_size: Option<String>,
    pub only_dir: Option<bool>,
    pub only_file: Option<bool>,
    pub disable_progress: Option<bool>,
    pub depth: Option<usize>,
    pub bars_on_right: Option<bool>,
    pub stack_size: Option<usize>,
    pub threads: Option<usize>,
    pub output_json: Option<bool>,
    pub print_errors: Option<bool>,
    pub files0_from: Option<String>,
}

impl Config {
    pub fn get_files_from(&self, options: &ArgMatches) -> Option<String> {
        let from_file = options.get_one::<String>("files0_from");
        match from_file {
            None => self.files0_from.as_ref().map(|x| x.to_string()),
            Some(x) => Some(x.to_string()),
        }
    }
    pub fn get_no_colors(&self, options: &ArgMatches) -> bool {
        Some(true) == self.no_colors || options.get_flag("no_colors")
    }
    pub fn get_force_colors(&self, options: &ArgMatches) -> bool {
        Some(true) == self.force_colors || options.get_flag("force_colors")
    }
    pub fn get_disable_progress(&self, options: &ArgMatches) -> bool {
        Some(true) == self.disable_progress
            || options.get_flag("disable_progress")
            || !std::io::stdout().is_terminal()
    }
    pub fn get_apparent_size(&self, options: &ArgMatches) -> bool {
        Some(true) == self.display_apparent_size || options.get_flag("display_apparent_size")
    }
    pub fn get_ignore_hidden(&self, options: &ArgMatches) -> bool {
        Some(true) == self.ignore_hidden || options.get_flag("ignore_hidden")
    }
    pub fn get_full_paths(&self, options: &ArgMatches) -> bool {
        Some(true) == self.display_full_paths || options.get_flag("display_full_paths")
    }
    pub fn get_reverse(&self, options: &ArgMatches) -> bool {
        Some(true) == self.reverse || options.get_flag("reverse")
    }
    pub fn get_no_bars(&self, options: &ArgMatches) -> bool {
        Some(true) == self.no_bars || options.get_flag("no_bars")
    }
    pub fn get_output_format(&self, options: &ArgMatches) -> String {
        let out_fmt = options.get_one::<String>("output_format");
        (match out_fmt {
            None => match &self.output_format {
                None => "".to_string(),
                Some(x) => x.to_string(),
            },
            Some(x) => x.into(),
        })
        .to_lowercase()
    }
    pub fn get_skip_total(&self, options: &ArgMatches) -> bool {
        Some(true) == self.skip_total || options.get_flag("skip_total")
    }
    pub fn get_screen_reader(&self, options: &ArgMatches) -> bool {
        Some(true) == self.screen_reader || options.get_flag("screen_reader")
    }
    pub fn get_depth(&self, options: &ArgMatches) -> usize {
        if let Some(v) = options.get_one::<usize>("depth") {
            return *v;
        }

        self.depth.unwrap_or(usize::MAX)
    }
    pub fn get_min_size(&self, options: &ArgMatches) -> Option<usize> {
        let size_from_param = options.get_one::<String>("min_size");
        self._get_min_size(size_from_param)
    }
    fn _get_min_size(&self, min_size: Option<&String>) -> Option<usize> {
        let size_from_param = min_size.and_then(|a| convert_min_size(a));

        if size_from_param.is_none() {
            self.min_size
                .as_ref()
                .and_then(|a| convert_min_size(a.as_ref()))
        } else {
            size_from_param
        }
    }
    pub fn get_only_dir(&self, options: &ArgMatches) -> bool {
        Some(true) == self.only_dir || options.get_flag("only_dir")
    }

    pub fn get_print_errors(&self, options: &ArgMatches) -> bool {
        Some(true) == self.print_errors || options.get_flag("print_errors")
    }
    pub fn get_only_file(&self, options: &ArgMatches) -> bool {
        Some(true) == self.only_file || options.get_flag("only_file")
    }
    pub fn get_bars_on_right(&self, options: &ArgMatches) -> bool {
        Some(true) == self.bars_on_right || options.get_flag("bars_on_right")
    }
    pub fn get_custom_stack_size(&self, options: &ArgMatches) -> Option<usize> {
        let from_cmd_line = options.get_one::<usize>("stack_size");
        if from_cmd_line.is_none() {
            self.stack_size
        } else {
            from_cmd_line.copied()
        }
    }
    pub fn get_threads(&self, options: &ArgMatches) -> Option<usize> {
        let from_cmd_line = options.get_one::<usize>("threads");
        if from_cmd_line.is_none() {
            self.threads
        } else {
            from_cmd_line.copied()
        }
    }
    pub fn get_output_json(&self, options: &ArgMatches) -> bool {
        Some(true) == self.output_json || options.get_flag("output_json")
    }

    pub fn get_modified_time_operator(&self, options: &ArgMatches) -> Option<(Operater, i64)> {
        get_filter_time_operator(
            options.get_one::<String>("mtime"),
            get_current_date_epoch_seconds(),
        )
    }

    pub fn get_accessed_time_operator(&self, options: &ArgMatches) -> Option<(Operater, i64)> {
        get_filter_time_operator(
            options.get_one::<String>("atime"),
            get_current_date_epoch_seconds(),
        )
    }

    pub fn get_created_time_operator(&self, options: &ArgMatches) -> Option<(Operater, i64)> {
        get_filter_time_operator(
            options.get_one::<String>("ctime"),
            get_current_date_epoch_seconds(),
        )
    }
}

fn get_current_date_epoch_seconds() -> i64 {
    // calcurate current date epoch seconds
    let now = Local::now();
    let current_date = now.date_naive();

    let current_date_time = current_date.and_hms_opt(0, 0, 0).unwrap();
    Local
        .from_local_datetime(&current_date_time)
        .unwrap()
        .timestamp()
}

fn get_filter_time_operator(
    option_value: Option<&String>,
    current_date_epoch_seconds: i64,
) -> Option<(Operater, i64)> {
    match option_value {
        Some(val) => {
            let time = current_date_epoch_seconds
                - val
                    .parse::<i64>()
                    .unwrap_or_else(|_| panic!("invalid data format"))
                    .abs()
                    * DAY_SECONDS;
            match val.chars().next().expect("Value should not be empty") {
                '+' => Some((Operater::LessThan, time - DAY_SECONDS)),
                '-' => Some((Operater::GreaterThan, time)),
                _ => Some((Operater::Equal, time - DAY_SECONDS)),
            }
        }
        None => None,
    }
}

fn convert_min_size(input: &str) -> Option<usize> {
    let re = Regex::new(r"([0-9]+)(\w*)").unwrap();

    if let Some(cap) = re.captures(input) {
        let (_, [digits, letters]) = cap.extract();

        // Failure to parse should be impossible due to regex match
        let digits_as_usize: Option<usize> = digits.parse().ok();

        match digits_as_usize {
            Some(parsed_digits) => {
                let number_format = get_number_format(&letters.to_lowercase());
                match number_format {
                    Some((multiple, _)) => Some(parsed_digits * (multiple as usize)),
                    None => {
                        if letters.eq("") {
                            Some(parsed_digits)
                        } else {
                            eprintln!("Ignoring invalid min-size: {input}");
                            None
                        }
                    }
                }
            }
            None => None,
        }
    } else {
        None
    }
}

fn get_config_locations(base: &Path) -> Vec<PathBuf> {
    vec![
        base.join(".dust.toml"),
        base.join(".config").join("dust").join("config.toml"),
    ]
}

pub fn get_config() -> Config {
    if let Some(home) = directories::BaseDirs::new() {
        for path in get_config_locations(home.home_dir()) {
            if path.exists() {
                if let Ok(config) = Config::from_config_file(path) {
                    return config;
                }
            }
        }
    }
    Config {
        ..Default::default()
    }
}

#[cfg(test)]
mod tests {
    #[allow(unused_imports)]
    use super::*;
    use chrono::{Datelike, Timelike};
    use clap::{value_parser, Arg, ArgMatches, Command};

    #[test]
    fn test_get_current_date_epoch_seconds() {
        let epoch_seconds = get_current_date_epoch_seconds();
        let dt = Local.timestamp_opt(epoch_seconds, 0).unwrap();

        assert_eq!(dt.hour(), 0);
        assert_eq!(dt.minute(), 0);
        assert_eq!(dt.second(), 0);
        assert_eq!(dt.date_naive().day(), Local::now().date_naive().day());
        assert_eq!(dt.date_naive().month(), Local::now().date_naive().month());
        assert_eq!(dt.date_naive().year(), Local::now().date_naive().year());
    }

    #[test]
    fn test_conversion() {
        assert_eq!(convert_min_size("55"), Some(55));
        assert_eq!(convert_min_size("12344321"), Some(12344321));
        assert_eq!(convert_min_size("95RUBBISH"), None);
        assert_eq!(convert_min_size("10Ki"), Some(10 * 1024));
        assert_eq!(convert_min_size("10MiB"), Some(10 * 1024usize.pow(2)));
        assert_eq!(convert_min_size("10M"), Some(10 * 1024usize.pow(2)));
        assert_eq!(convert_min_size("10Mb"), Some(10 * 1000usize.pow(2)));
        assert_eq!(convert_min_size("2Gi"), Some(2 * 1024usize.pow(3)));
    }

    #[test]
    fn test_min_size_from_config_applied_or_overridden() {
        let c = Config {
            min_size: Some("1KiB".to_owned()),
            ..Default::default()
        };
        assert_eq!(c._get_min_size(None), Some(1024));
        assert_eq!(c._get_min_size(Some(&"2KiB".into())), Some(2048));

        assert_eq!(c._get_min_size(Some(&"1kb".into())), Some(1000));
        assert_eq!(c._get_min_size(Some(&"2KB".into())), Some(2000));
    }

    #[test]
    fn test_get_depth() {
        // No config and no flag.
        let c = Config::default();
        let args = get_args(vec![]);
        assert_eq!(c.get_depth(&args), usize::MAX);

        // Config is not defined and flag is defined.
        let c = Config::default();
        let args = get_args(vec!["dust", "--depth", "5"]);
        assert_eq!(c.get_depth(&args), 5);

        // Config is defined and flag is not defined.
        let c = Config {
            depth: Some(3),
            ..Default::default()
        };
        let args = get_args(vec![]);
        assert_eq!(c.get_depth(&args), 3);

        // Both config and flag are defined.
        let c = Config {
            depth: Some(3),
            ..Default::default()
        };
        let args = get_args(vec!["dust", "--depth", "5"]);
        assert_eq!(c.get_depth(&args), 5);
    }

    fn get_args(args: Vec<&str>) -> ArgMatches {
        Command::new("Dust")
            .arg(
                Arg::new("depth")
                    .long("depth")
                    .num_args(1)
                    .value_parser(value_parser!(usize)),
            )
            .get_matches_from(args)
    }
}
07070100000016000081A400000000000000000000000166980F0000002BB2000000000000000000000000000000000000001D00000000dust-1.1.1/src/dir_walker.rsuse std::cmp::Ordering;
use std::fs;
use std::sync::Arc;
use std::sync::Mutex;

use crate::node::Node;
use crate::progress::Operation;
use crate::progress::PAtomicInfo;
use crate::progress::RuntimeErrors;
use crate::progress::ORDERING;
use crate::utils::is_filtered_out_due_to_file_time;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use regex::Regex;
use std::path::PathBuf;

use std::collections::HashSet;

use crate::node::build_node;
use std::fs::DirEntry;

use crate::platform::get_metadata;

#[derive(Debug)]
pub enum Operater {
    Equal = 0,
    LessThan = 1,
    GreaterThan = 2,
}

pub struct WalkData<'a> {
    pub ignore_directories: HashSet<PathBuf>,
    pub filter_regex: &'a [Regex],
    pub invert_filter_regex: &'a [Regex],
    pub allowed_filesystems: HashSet<u64>,
    pub filter_modified_time: Option<(Operater, i64)>,
    pub filter_accessed_time: Option<(Operater, i64)>,
    pub filter_changed_time: Option<(Operater, i64)>,
    pub use_apparent_size: bool,
    pub by_filecount: bool,
    pub ignore_hidden: bool,
    pub follow_links: bool,
    pub progress_data: Arc<PAtomicInfo>,
    pub errors: Arc<Mutex<RuntimeErrors>>,
}

pub fn walk_it(dirs: HashSet<PathBuf>, walk_data: &WalkData) -> Vec<Node> {
    let mut inodes = HashSet::new();
    let top_level_nodes: Vec<_> = dirs
        .into_iter()
        .filter_map(|d| {
            let prog_data = &walk_data.progress_data;
            prog_data.clear_state(&d);
            let node = walk(d, walk_data, 0)?;

            prog_data.state.store(Operation::PREPARING, ORDERING);

            clean_inodes(node, &mut inodes, walk_data.use_apparent_size)
        })
        .collect();
    top_level_nodes
}

// Remove files which have the same inode, we don't want to double count them.
fn clean_inodes(
    x: Node,
    inodes: &mut HashSet<(u64, u64)>,
    use_apparent_size: bool,
) -> Option<Node> {
    if !use_apparent_size {
        if let Some(id) = x.inode_device {
            if !inodes.insert(id) {
                return None;
            }
        }
    }

    // Sort Nodes so iteration order is predictable
    let mut tmp: Vec<_> = x.children;
    tmp.sort_by(sort_by_inode);
    let new_children: Vec<_> = tmp
        .into_iter()
        .filter_map(|c| clean_inodes(c, inodes, use_apparent_size))
        .collect();

    Some(Node {
        name: x.name,
        size: x.size + new_children.iter().map(|c| c.size).sum::<u64>(),
        children: new_children,
        inode_device: x.inode_device,
        depth: x.depth,
    })
}

fn sort_by_inode(a: &Node, b: &Node) -> std::cmp::Ordering {
    // Sorting by inode is quicker than by sorting by name/size
    match (a.inode_device, b.inode_device) {
        (Some(x), Some(y)) => {
            if x.0 != y.0 {
                x.0.cmp(&y.0)
            } else if x.1 != y.1 {
                x.1.cmp(&y.1)
            } else {
                a.name.cmp(&b.name)
            }
        }
        (Some(_), None) => Ordering::Greater,
        (None, Some(_)) => Ordering::Less,
        (None, None) => a.name.cmp(&b.name),
    }
}

fn ignore_file(entry: &DirEntry, walk_data: &WalkData) -> bool {
    let is_dot_file = entry.file_name().to_str().unwrap_or("").starts_with('.');
    let is_ignored_path = walk_data.ignore_directories.contains(&entry.path());

    if !walk_data.allowed_filesystems.is_empty() {
        let size_inode_device = get_metadata(entry.path(), false);
        if let Some((_size, Some((_id, dev)), _gunk)) = size_inode_device {
            if !walk_data.allowed_filesystems.contains(&dev) {
                return true;
            }
        }
    }
    if walk_data.filter_accessed_time.is_some()
        || walk_data.filter_modified_time.is_some()
        || walk_data.filter_changed_time.is_some()
    {
        let size_inode_device = get_metadata(entry.path(), false);
        if let Some((_, _, (modified_time, accessed_time, changed_time))) = size_inode_device {
            if entry.path().is_file()
                && [
                    (&walk_data.filter_modified_time, modified_time),
                    (&walk_data.filter_accessed_time, accessed_time),
                    (&walk_data.filter_changed_time, changed_time),
                ]
                .iter()
                .any(|(filter_time, actual_time)| {
                    is_filtered_out_due_to_file_time(filter_time, *actual_time)
                })
            {
                return true;
            }
        }
    }

    // Keeping `walk_data.filter_regex.is_empty()` is important for performance reasons, it stops unnecessary work
    if !walk_data.filter_regex.is_empty()
        && entry.path().is_file()
        && is_filtered_out_due_to_regex(walk_data.filter_regex, &entry.path())
    {
        return true;
    }

    if !walk_data.invert_filter_regex.is_empty()
        && entry.path().is_file()
        && is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &entry.path())
    {
        return true;
    }

    (is_dot_file && walk_data.ignore_hidden) || is_ignored_path
}

fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option<Node> {
    let prog_data = &walk_data.progress_data;
    let errors = &walk_data.errors;

    if errors.lock().unwrap().abort {
        return None;
    }

    let children = if dir.is_dir() {
        let read_dir = fs::read_dir(&dir);
        match read_dir {
            Ok(entries) => {
                entries
                    .into_iter()
                    .par_bridge()
                    .filter_map(|entry| {
                        match entry {
                            Ok(ref entry) => {
                                // uncommenting the below line gives simpler code but
                                // rayon doesn't parallelize as well giving a 3X performance drop
                                // hence we unravel the recursion a bit

                                // return walk(entry.path(), walk_data, depth)

                                if !ignore_file(entry, walk_data) {
                                    if let Ok(data) = entry.file_type() {
                                        if data.is_dir()
                                            || (walk_data.follow_links && data.is_symlink())
                                        {
                                            return walk(entry.path(), walk_data, depth + 1);
                                        }

                                        let node = build_node(
                                            entry.path(),
                                            vec![],
                                            data.is_symlink(),
                                            data.is_file(),
                                            depth,
                                            walk_data,
                                        );

                                        prog_data.num_files.fetch_add(1, ORDERING);
                                        if let Some(ref file) = node {
                                            prog_data
                                                .total_file_size
                                                .fetch_add(file.size, ORDERING);
                                        }

                                        return node;
                                    }
                                }
                            }
                            Err(ref failed) => {
                                let mut editable_error = errors.lock().unwrap();
                                editable_error.no_permissions.insert(failed.to_string());
                            }
                        }
                        None
                    })
                    .collect()
            }
            Err(failed) => {
                let mut editable_error = errors.lock().unwrap();
                match failed.kind() {
                    std::io::ErrorKind::PermissionDenied => {
                        editable_error
                            .no_permissions
                            .insert(dir.to_string_lossy().into());
                    }
                    std::io::ErrorKind::NotFound => {
                        editable_error.file_not_found.insert(failed.to_string());
                    }
                    _ => {
                        editable_error.unknown_error.insert(failed.to_string());
                    }
                }
                vec![]
            }
        }
    } else {
        if !dir.is_file() {
            let mut editable_error = errors.lock().unwrap();
            let bad_file = dir.as_os_str().to_string_lossy().into();
            editable_error.file_not_found.insert(bad_file);
        }
        vec![]
    };
    build_node(dir, children, false, false, depth, walk_data)
}

mod tests {

    #[allow(unused_imports)]
    use super::*;

    #[cfg(test)]
    fn create_node() -> Node {
        Node {
            name: PathBuf::new(),
            size: 10,
            children: vec![],
            inode_device: Some((5, 6)),
            depth: 0,
        }
    }

    #[test]
    #[allow(clippy::redundant_clone)]
    fn test_should_ignore_file() {
        let mut inodes = HashSet::new();
        let n = create_node();

        // First time we insert the node
        assert_eq!(clean_inodes(n.clone(), &mut inodes, false), Some(n.clone()));

        // Second time is a duplicate - we ignore it
        assert_eq!(clean_inodes(n.clone(), &mut inodes, false), None);
    }

    #[test]
    #[allow(clippy::redundant_clone)]
    fn test_should_not_ignore_files_if_using_apparent_size() {
        let mut inodes = HashSet::new();
        let n = create_node();

        // If using apparent size we include Nodes, even if duplicate inodes
        assert_eq!(clean_inodes(n.clone(), &mut inodes, true), Some(n.clone()));
        assert_eq!(clean_inodes(n.clone(), &mut inodes, true), Some(n.clone()));
    }

    #[test]
    fn test_total_ordering_of_sort_by_inode() {
        use std::str::FromStr;

        let a = Node {
            name: PathBuf::from_str("a").unwrap(),
            size: 0,
            children: vec![],
            inode_device: Some((3, 66310)),
            depth: 0,
        };

        let b = Node {
            name: PathBuf::from_str("b").unwrap(),
            size: 0,
            children: vec![],
            inode_device: None,
            depth: 0,
        };

        let c = Node {
            name: PathBuf::from_str("c").unwrap(),
            size: 0,
            children: vec![],
            inode_device: Some((1, 66310)),
            depth: 0,
        };

        assert_eq!(sort_by_inode(&a, &b), Ordering::Greater);
        assert_eq!(sort_by_inode(&a, &c), Ordering::Greater);
        assert_eq!(sort_by_inode(&c, &b), Ordering::Greater);

        assert_eq!(sort_by_inode(&b, &a), Ordering::Less);
        assert_eq!(sort_by_inode(&c, &a), Ordering::Less);
        assert_eq!(sort_by_inode(&b, &c), Ordering::Less);
    }
}
07070100000017000081A400000000000000000000000166980F0000004FFA000000000000000000000000000000000000001A00000000dust-1.1.1/src/display.rsuse crate::display_node::DisplayNode;

use 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;

pub static UNITS: [char; 4] = ['T', 'G', 'M', 'K'];
static BLOCKS: [char; 5] = ['β–ˆ', 'β–“', 'β–’', 'β–‘', ' '];

pub struct InitialDisplayData {
    pub short_paths: bool,
    pub is_reversed: bool,
    pub colors_on: bool,
    pub by_filecount: bool,
    pub is_screen_reader: bool,
    pub output_format: String,
    pub bars_on_right: bool,
}

pub struct DisplayData {
    pub initial: InitialDisplayData,
    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.initial.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.initial.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.initial.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 {
        if self.display_data.initial.is_screen_reader {
            return level.to_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 - level.clamp(1, 4);

        let itr: Box<dyn Iterator<Item = char>> = if self.display_data.initial.bars_on_right {
            Box::new(self.percent_bar.chars())
        } else {
            Box::new(self.percent_bar.chars().rev())
        };

        for c in itr {
            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);
            }
        }
        if self.display_data.initial.bars_on_right {
            new_bar
        } else {
            new_bar.chars().rev().collect()
        }
    }
}

pub fn draw_it(
    idd: InitialDisplayData,
    no_percent_bars: bool,
    terminal_width: usize,
    root_node: &DisplayNode,
    skip_total: bool,
) {
    let biggest = match skip_total {
        false => root_node,
        true => root_node
            .get_children_from_node(false)
            .next()
            .unwrap_or(root_node),
    };

    let num_chars_needed_on_left_most = if idd.by_filecount {
        let max_size = biggest.size;
        max_size.separate_with_commas().chars().count()
    } else {
        find_biggest_size_str(root_node, &idd.output_format)
    };

    assert!(
        terminal_width > num_chars_needed_on_left_most + 2,
        "Not enough terminal width"
    );

    let allowed_width = terminal_width - num_chars_needed_on_left_most - 2;
    let num_indent_chars = 3;
    let longest_string_length =
        find_longest_dir_name(root_node, num_indent_chars, allowed_width, &idd);

    let max_bar_length = if no_percent_bars || longest_string_length + 7 >= allowed_width {
        0
    } else {
        allowed_width - longest_string_length - 7
    };

    let first_size_bar = repeat(BLOCKS[0]).take(max_bar_length).collect();

    let display_data = DisplayData {
        initial: idd,
        num_chars_needed_on_left_most,
        base_size: biggest.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,
    };

    if !skip_total {
        display_node(root_node, &draw_data, true, true);
    } else {
        for (count, c) in root_node
            .get_children_from_node(draw_data.display_data.initial.is_reversed)
            .enumerate()
        {
            let is_biggest = display_data.is_biggest(count, root_node.num_siblings());
            let was_i_last = display_data.is_last(count, root_node.num_siblings());
            display_node(c, &draw_data, is_biggest, was_i_last);
        }
    }
}

fn find_biggest_size_str(node: &DisplayNode, output_format: &str) -> usize {
    let mut mx = human_readable_number(node.size, output_format)
        .chars()
        .count();
    for n in node.children.iter() {
        mx = max(mx, find_biggest_size_str(n, output_format));
    }
    mx
}

fn find_longest_dir_name(
    node: &DisplayNode,
    indent: usize,
    terminal: usize,
    idd: &InitialDisplayData,
) -> usize {
    let printable_name = get_printable_name(&node.name, idd.short_paths);

    let longest = if idd.is_screen_reader {
        UnicodeWidthStr::width(&*printable_name) + 1
    } else {
        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, idd))
        .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.initial.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.initial.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.initial.is_reversed {
        println!("{to_print}")
    }
}

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_printable_name<P: AsRef<Path>>(dir_name: &P, short_paths: bool) -> String {
    let dir_name = dir_name.as_ref();
    let printable_name = {
        if short_paths {
            match dir_name.parent() {
                Some(prefix) => match dir_name.strip_prefix(prefix) {
                    Ok(base) => base,
                    Err(_) => dir_name,
                },
                None => dir_name,
            }
        } else {
            dir_name
        }
    };
    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.initial.short_paths);
    let indent_and_name = format!("{indent} {name}");
    let width = UnicodeWidthStr::width(&*indent_and_name);

    assert!(
        display_data.longest_string_length >= width,
        "Terminal width not wide enough to draw directory tree"
    );

    // Add spaces after the filename so we can draw the % used bar chart.
    let name_and_padding = name
        + " "
            .repeat(display_data.longest_string_length - width)
            .as_str();

    name_and_padding
}

fn maybe_trim_filename(name_in: String, indent: &str, display_data: &DisplayData) -> String {
    let indent_length = UnicodeWidthStr::width(indent);
    assert!(
        display_data.longest_string_length >= indent_length + 2,
        "Terminal width not wide enough to draw directory tree"
    );

    let max_size = display_data.longest_string_length - indent_length;
    if UnicodeWidthStr::width(&*name_in) > max_size {
        let name = name_in.chars().take(max_size - 2).collect::<String>();
        name + ".."
    } else {
        name_in
    }
}

pub fn format_string(
    node: &DisplayNode,
    indent: &str,
    bars: &str,
    is_biggest: bool,
    display_data: &DisplayData,
) -> String {
    let (percent, name_and_padding) = get_name_percent(node, indent, bars, 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);
    // we can clean this and the method below somehow, not sure yet
    if display_data.initial.is_screen_reader {
        // if screen_reader then bars is 'depth'
        format!("{pretty_name} {bars} {pretty_size}{percent}")
    } else {
        format!("{pretty_size} {indent} {pretty_name}{percent}")
    }
}

fn get_name_percent(
    node: &DisplayNode,
    indent: &str,
    bar_chart: &str,
    display_data: &DisplayData,
) -> (String, String) {
    if display_data.initial.is_screen_reader {
        let percent = display_data.percent_size(node) * 100.0;
        let percent_size_str = format!("{percent:.0}%");
        let percents = format!(" {percent_size_str:>4}",);
        let name = pad_or_trim_filename(node, "", display_data);
        (percents, name)
    // Bar chart being empty may come from either config or the screen not being wide enough
    } else if !bar_chart.is_empty() {
        let percent = display_data.percent_size(node) * 100.0;
        let percent_size_str = format!("{percent:.0}%");
        let percents = format!("β”‚{bar_chart} β”‚ {percent_size_str:>4}");
        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.initial.short_paths);
        let name = maybe_trim_filename(n, indent, display_data);
        ("".into(), name)
    }
}

fn get_pretty_size(node: &DisplayNode, is_biggest: bool, display_data: &DisplayData) -> String {
    let output = if display_data.initial.by_filecount {
        node.size.separate_with_commas()
    } else {
        human_readable_number(node.size, &display_data.initial.output_format)
    };
    let spaces_to_add = display_data.num_chars_needed_on_left_most - output.chars().count();
    let output = " ".repeat(spaces_to_add) + output.as_str();

    if is_biggest && display_data.initial.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.initial.colors_on {
        let meta_result = fs::metadata(&node.name);
        let directory_color = display_data
            .ls_colors
            .style_for_path_with_metadata(&node.name, meta_result.as_ref().ok());
        let ansi_style = directory_color
            .map(Style::to_ansi_term_style)
            .unwrap_or_default();
        let out = ansi_style.paint(name_and_padding);
        format!("{out}")
    } else {
        name_and_padding
    }
}

// If we are working with SI units or not
pub fn get_type_of_thousand(output_str: &str) -> u64 {
    if output_str.is_empty() {
        1024
    } else if output_str == "si" {
        1000
    } else if output_str.contains('i') || output_str.len() == 1 {
        1024
    } else {
        1000
    }
}

pub fn get_number_format(output_str: &str) -> Option<(u64, char)> {
    if output_str.starts_with('b') {
        return Some((1, 'B'));
    }
    for (i, u) in UNITS.iter().enumerate() {
        if output_str.starts_with((*u).to_ascii_lowercase()) {
            let marker = get_type_of_thousand(output_str).pow((UNITS.len() - i) as u32);
            return Some((marker, *u));
        }
    }
    None
}

pub fn human_readable_number(size: u64, output_str: &str) -> String {
    match get_number_format(output_str) {
        Some((x, u)) => {
            format!("{}{}", (size / x), u)
        }
        None => {
            for (i, u) in UNITS.iter().enumerate() {
                let marker = get_type_of_thousand(output_str).pow((UNITS.len() - i) as u32);
                if size >= marker {
                    if size / marker < 10 {
                        return format!("{:.1}{}", (size as f32 / marker as f32), u);
                    } else {
                        return format!("{}{}", (size / marker), u);
                    }
                }
            }
            format!("{size}B")
        }
    }
}

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 {
        let initial = InitialDisplayData {
            short_paths: true,
            is_reversed: false,
            colors_on: false,
            by_filecount: false,
            is_screen_reader: false,
            output_format: "".into(),
            bars_on_right: false,
        };
        DisplayData {
            initial,
            num_chars_needed_on_left_most: 5,
            base_size: 2_u64.pow(12), // 4.0K
            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 data = get_fake_display_data(20);

        let s = format_string(&n, indent, percent_bar, is_biggest, &data);
        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 data = get_fake_display_data(64);
        let s = format_string(&n, indent, percent_bar, is_biggest, &data);
        assert_eq!(
            s,
            " 4.0K β”Œβ”€β”΄ very_long_name_longer_than_the_eighty_character_limit_very_.."
        );
    }

    #[test]
    fn test_format_str_screen_reader() {
        let n = DisplayNode {
            name: PathBuf::from("/short"),
            size: 2_u64.pow(12), // This is 4.0K
            children: vec![],
        };
        let indent = "";
        let percent_bar = "3";
        let is_biggest = false;
        let mut data = get_fake_display_data(20);
        data.initial.is_screen_reader = true;

        let s = format_string(&n, indent, percent_bar, is_biggest, &data);
        assert_eq!(s, "short               3  4.0K 100%");
    }

    #[test]
    fn test_human_readable_number() {
        assert_eq!(human_readable_number(1, ""), "1B");
        assert_eq!(human_readable_number(956, ""), "956B");
        assert_eq!(human_readable_number(1004, ""), "1004B");
        assert_eq!(human_readable_number(1024, ""), "1.0K");
        assert_eq!(human_readable_number(1536, ""), "1.5K");
        assert_eq!(human_readable_number(1024 * 512, ""), "512K");
        assert_eq!(human_readable_number(1024 * 1024, ""), "1.0M");
        assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1, ""), "1023M");
        assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20, ""), "20G");
        assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024, ""), "1.0T");
    }

    #[test]
    fn test_human_readable_number_si() {
        assert_eq!(human_readable_number(1024 * 100, ""), "100K");
        assert_eq!(human_readable_number(1024 * 100, "si"), "102K");
    }

    // Refer to https://en.wikipedia.org/wiki/Byte#Multiple-byte_units
    #[test]
    fn test_human_readable_number_kb() {
        let hrn = human_readable_number;
        assert_eq!(hrn(1023, "b"), "1023B");
        assert_eq!(hrn(1000 * 1000, "bytes"), "1000000B");
        assert_eq!(hrn(1023, "kb"), "1K");
        assert_eq!(hrn(1023, "k"), "0K");
        assert_eq!(hrn(1023, "kib"), "0K");
        assert_eq!(hrn(1024, "kib"), "1K");
        assert_eq!(hrn(1024 * 512, "kib"), "512K");
        assert_eq!(hrn(1024 * 1024, "kib"), "1024K");
        assert_eq!(hrn(1024 * 1000 * 1000 * 20, "kib"), "20000000K");
        assert_eq!(hrn(1024 * 1024 * 1000 * 20, "mib"), "20000M");
        assert_eq!(hrn(1024 * 1024 * 1024 * 20, "gib"), "20G");
    }

    #[cfg(test)]
    fn build_draw_data(disp: &DisplayData, size: u32) -> (DrawData<'_>, DisplayNode) {
        let n = DisplayNode {
            name: PathBuf::from("/short"),
            size: 2_u64.pow(size),
            children: vec![],
        };
        let first_size_bar = repeat(BLOCKS[0]).take(13).collect();
        let dd = DrawData {
            indent: "".into(),
            percent_bar: first_size_bar,
            display_data: disp,
        };
        (dd, n)
    }

    #[test]
    fn test_draw_data() {
        let disp = &get_fake_display_data(20);
        let (dd, n) = build_draw_data(disp, 12);
        let bar = dd.generate_bar(&n, 1);
        assert_eq!(bar, "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ");
    }

    #[test]
    fn test_draw_data2() {
        let disp = &get_fake_display_data(20);
        let (dd, n) = build_draw_data(disp, 11);
        let bar = dd.generate_bar(&n, 2);
        assert_eq!(bar, "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘");
    }
    #[test]
    fn test_draw_data3() {
        let mut disp = get_fake_display_data(20);
        let (dd, n) = build_draw_data(&disp, 11);
        let bar = dd.generate_bar(&n, 3);
        assert_eq!(bar, "β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–’β–’β–’β–’β–’β–’");

        disp.initial.bars_on_right = true;
        let (dd, n) = build_draw_data(&disp, 11);
        let bar = dd.generate_bar(&n, 3);
        assert_eq!(bar, "β–’β–’β–’β–’β–’β–’β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ")
    }
    #[test]
    fn test_draw_data4() {
        let disp = &get_fake_display_data(20);
        let (dd, n) = build_draw_data(disp, 10);
        // After 4 we have no more levels of shading so 4+ is the same
        let bar = dd.generate_bar(&n, 4);
        assert_eq!(bar, "β–ˆβ–ˆβ–ˆβ–ˆβ–“β–“β–“β–“β–“β–“β–“β–“β–“");
        let bar = dd.generate_bar(&n, 5);
        assert_eq!(bar, "β–ˆβ–ˆβ–ˆβ–ˆβ–“β–“β–“β–“β–“β–“β–“β–“β–“");
    }
}
07070100000018000081A400000000000000000000000166980F0000000301000000000000000000000000000000000000001F00000000dust-1.1.1/src/display_node.rsuse std::path::PathBuf;

use serde::Serialize;

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize)]
pub struct DisplayNode {
    // Note: the order of fields in important here, for PartialEq and PartialOrd
    pub size: u64,
    pub name: PathBuf,
    pub children: Vec<DisplayNode>,
}

impl DisplayNode {
    pub fn num_siblings(&self) -> u64 {
        self.children.len() as u64
    }

    pub fn get_children_from_node(&self, is_reversed: bool) -> impl Iterator<Item = &DisplayNode> {
        // we box to avoid the clippy lint warning
        let out: Box<dyn Iterator<Item = &DisplayNode>> = if is_reversed {
            Box::new(self.children.iter().rev())
        } else {
            Box::new(self.children.iter())
        };
        out
    }
}
07070100000019000081A400000000000000000000000166980F000000106D000000000000000000000000000000000000001900000000dust-1.1.1/src/filter.rsuse crate::display_node::DisplayNode;
use crate::node::Node;
use std::collections::BinaryHeap;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;

pub struct AggregateData {
    pub min_size: Option<usize>,
    pub only_dir: bool,
    pub only_file: bool,
    pub number_of_lines: usize,
    pub depth: usize,
    pub using_a_filter: bool,
}

pub fn get_biggest(top_level_nodes: Vec<Node>, display_data: AggregateData) -> 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;

    if number_top_level_nodes > 1 {
        let size = top_level_nodes.iter().map(|node| node.size).sum();
        root = Node {
            name: PathBuf::from("(total)"),
            size,
            children: top_level_nodes,
            inode_device: None,
            depth: 0,
        };
        // Always include the base nodes if we add a 'parent' (total) node
        heap = always_add_children(&display_data, &root, heap);
    } else {
        root = top_level_nodes.into_iter().next().unwrap();
        heap = add_children(&display_data, &root, heap);
    }

    Some(fill_remaining_lines(heap, &root, display_data))
}

pub fn fill_remaining_lines<'a>(
    mut heap: BinaryHeap<&'a Node>,
    root: &'a Node,
    display_data: AggregateData,
) -> DisplayNode {
    let mut allowed_nodes = HashMap::new();

    while allowed_nodes.len() < display_data.number_of_lines {
        let line = heap.pop();
        match line {
            Some(line) => {
                if !display_data.only_file || line.children.is_empty() {
                    allowed_nodes.insert(line.name.as_path(), line);
                }
                heap = add_children(&display_data, line, heap);
            }
            None => break,
        }
    }

    if display_data.only_file {
        flat_rebuilder(allowed_nodes, root)
    } else {
        recursive_rebuilder(&allowed_nodes, root)
    }
}

fn add_children<'a>(
    display_data: &AggregateData,
    file_or_folder: &'a Node,
    heap: BinaryHeap<&'a Node>,
) -> BinaryHeap<&'a Node> {
    if display_data.depth > file_or_folder.depth {
        always_add_children(display_data, file_or_folder, heap)
    } else {
        heap
    }
}

fn always_add_children<'a>(
    display_data: &AggregateData,
    file_or_folder: &'a Node,
    mut heap: BinaryHeap<&'a Node>,
) -> BinaryHeap<&'a Node> {
    heap.extend(
        file_or_folder
            .children
            .iter()
            .filter(|c| match display_data.min_size {
                Some(ms) => c.size > ms as u64,
                None => !display_data.using_a_filter || c.name.is_file() || c.size > 0,
            })
            .filter(|c| {
                if display_data.only_dir {
                    c.name.is_dir()
                } else {
                    true
                }
            }),
    );
    heap
}

// Finds children of current, if in allowed_nodes adds them as children to new DisplayNode
fn recursive_rebuilder(allowed_nodes: &HashMap<&Path, &Node>, current: &Node) -> DisplayNode {
    let new_children: Vec<_> = current
        .children
        .iter()
        .filter(|c| allowed_nodes.contains_key(c.name.as_path()))
        .map(|c| recursive_rebuilder(allowed_nodes, c))
        .collect();

    build_display_node(new_children, current)
}

// Applies all allowed nodes as children to current node
fn flat_rebuilder(allowed_nodes: HashMap<&Path, &Node>, current: &Node) -> DisplayNode {
    let new_children: Vec<DisplayNode> = allowed_nodes
        .into_values()
        .map(|v| DisplayNode {
            name: v.name.clone(),
            size: v.size,
            children: vec![],
        })
        .collect::<Vec<DisplayNode>>();
    build_display_node(new_children, current)
}

fn build_display_node(mut new_children: Vec<DisplayNode>, current: &Node) -> DisplayNode {
    new_children.sort_by(|lhs, rhs| lhs.cmp(rhs).reverse());
    DisplayNode {
        name: current.name.clone(),
        size: current.size,
        children: new_children,
    }
}
0707010000001A000081A400000000000000000000000166980F0000000917000000000000000000000000000000000000001E00000000dust-1.1.1/src/filter_type.rsuse crate::display_node::DisplayNode;
use crate::node::Node;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::PathBuf;

#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct ExtensionNode<'a> {
    size: u64,
    extension: Option<&'a OsStr>,
}

pub fn get_all_file_types(top_level_nodes: &[Node], n: usize) -> Option<DisplayNode> {
    let ext_nodes = {
        let mut extension_cumulative_sizes = HashMap::new();
        build_by_all_file_types(top_level_nodes, &mut extension_cumulative_sizes);

        let mut extension_cumulative_sizes: Vec<ExtensionNode<'_>> = extension_cumulative_sizes
            .iter()
            .map(|(&extension, &size)| ExtensionNode { extension, size })
            .collect();

        extension_cumulative_sizes.sort_by(|lhs, rhs| lhs.cmp(rhs).reverse());

        extension_cumulative_sizes
    };

    let mut ext_nodes_iter = ext_nodes.iter();

    // First, collect the first N - 1 nodes...
    let mut displayed: Vec<DisplayNode> = ext_nodes_iter
        .by_ref()
        .take(if n > 1 { n - 1 } else { 1 })
        .map(|node| DisplayNode {
            name: PathBuf::from(
                node.extension
                    .map(|ext| format!(".{}", ext.to_string_lossy()))
                    .unwrap_or_else(|| "(no extension)".to_owned()),
            ),
            size: node.size,
            children: vec![],
        })
        .collect();

    // ...then, aggregate the remaining nodes (if any) into a single  "(others)" node
    if ext_nodes_iter.len() > 0 {
        displayed.push(DisplayNode {
            name: PathBuf::from("(others)"),
            size: ext_nodes_iter.map(|node| node.size).sum(),
            children: vec![],
        });
    }

    let result = DisplayNode {
        name: PathBuf::from("(total)"),
        size: displayed.iter().map(|node| node.size).sum(),
        children: displayed,
    };

    Some(result)
}

fn build_by_all_file_types<'a>(
    top_level_nodes: &'a [Node],
    counter: &mut HashMap<Option<&'a OsStr>, u64>,
) {
    for node in top_level_nodes {
        if node.name.is_file() {
            let ext = node.name.extension();
            let cumulative_size = counter.entry(ext).or_default();
            *cumulative_size += node.size;
        }
        build_by_all_file_types(&node.children, counter)
    }
}
0707010000001B000081A400000000000000000000000166980F00000030E0000000000000000000000000000000000000001700000000dust-1.1.1/src/main.rsmod cli;
mod config;
mod dir_walker;
mod display;
mod display_node;
mod filter;
mod filter_type;
mod node;
mod platform;
mod progress;
mod utils;

use crate::cli::build_cli;
use crate::progress::RuntimeErrors;
use clap::parser::ValuesRef;
use dir_walker::WalkData;
use display::InitialDisplayData;
use filter::AggregateData;
use progress::PIndicator;
use regex::Error;
use std::collections::HashSet;
use std::env;
use std::fs::read_to_string;
use std::io;
use std::panic;
use std::process;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use sysinfo::{System, SystemExt};

use self::display::draw_it;
use config::get_config;
use dir_walker::walk_it;
use filter::get_biggest;
use filter_type::get_all_file_types;
use regex::Regex;
use std::cmp::max;
use std::path::PathBuf;
use terminal_size::{terminal_size, Height, Width};
use utils::get_filesystem_devices;
use utils::simplify_dir_names;

static DEFAULT_NUMBER_OF_LINES: usize = 30;
static DEFAULT_TERMINAL_WIDTH: usize = 80;

fn should_init_color(no_color: bool, force_color: bool) -> bool {
    if force_color {
        return true;
    }
    if no_color {
        return false;
    }
    // check if NO_COLOR is set
    // https://no-color.org/
    if env::var_os("NO_COLOR").is_some() {
        return false;
    }
    if terminal_size().is_none() {
        // we are not in a terminal, color may not be needed
        return false;
    }
    // we are in a terminal
    #[cfg(windows)]
    {
        // Required for windows 10
        // Fails to resolve for windows 8 so disable color
        match ansi_term::enable_ansi_support() {
            Ok(_) => true,
            Err(_) => {
                eprintln!("This version of Windows does not support ANSI colors");
                false
            }
        }
    }
    #[cfg(not(windows))]
    {
        true
    }
}

fn get_height_of_terminal() -> usize {
    // Simplify once https://github.com/eminence/terminal-size/pull/41 is
    // merged
    terminal_size()
        // Windows CI runners detect a terminal height of 0
        .map(|(_, Height(h))| max(h as usize, DEFAULT_NUMBER_OF_LINES))
        .unwrap_or(DEFAULT_NUMBER_OF_LINES)
        - 10
}

fn get_width_of_terminal() -> usize {
    // Simplify once https://github.com/eminence/terminal-size/pull/41 is
    // merged
    terminal_size()
        .map(|(Width(w), _)| match cfg!(windows) {
            // Windows CI runners detect a very low terminal width
            true => max(w as usize, DEFAULT_TERMINAL_WIDTH),
            false => w as usize,
        })
        .unwrap_or(DEFAULT_TERMINAL_WIDTH)
}

fn get_regex_value(maybe_value: Option<ValuesRef<String>>) -> Vec<Regex> {
    maybe_value
        .unwrap_or_default()
        .map(|reg| {
            Regex::new(reg).unwrap_or_else(|err| {
                eprintln!("Ignoring bad value for regex {err:?}");
                process::exit(1)
            })
        })
        .collect()
}

fn main() {
    let options = build_cli().get_matches();
    let config = get_config();

    let errors = RuntimeErrors::default();
    let error_listen_for_ctrlc = Arc::new(Mutex::new(errors));
    let errors_for_rayon = error_listen_for_ctrlc.clone();
    let errors_final = error_listen_for_ctrlc.clone();
    let is_in_listing = Arc::new(AtomicBool::new(false));
    let cloned_is_in_listing = Arc::clone(&is_in_listing);

    ctrlc::set_handler(move || {
        error_listen_for_ctrlc.lock().unwrap().abort = true;
        println!("\nAborting");
        if cloned_is_in_listing.load(Ordering::Relaxed) {
            process::exit(1);
        }
    })
    .expect("Error setting Ctrl-C handler");

    is_in_listing.store(true, Ordering::Relaxed);
    let target_dirs = match config.get_files_from(&options) {
        Some(path) => {
            if path == "-" {
                let mut targets_to_add = io::stdin()
                    .lines()
                    .map_while(Result::ok)
                    .collect::<Vec<String>>();

                if targets_to_add.is_empty() {
                    eprintln!("No input provided, defaulting to current directory");
                    targets_to_add.push(".".to_owned());
                }
                targets_to_add
            } else {
                // read file
                match read_to_string(path) {
                    Ok(file_content) => file_content.lines().map(|x| x.to_string()).collect(),
                    Err(e) => {
                        eprintln!("Error reading file: {e}");
                        vec![".".to_owned()]
                    }
                }
            }
        }
        None => match options.get_many::<String>("params") {
            Some(values) => values.cloned().collect(),
            None => vec![".".to_owned()],
        },
    };
    is_in_listing.store(false, Ordering::Relaxed);

    let summarize_file_types = options.get_flag("types");

    let filter_regexs = get_regex_value(options.get_many("filter"));
    let invert_filter_regexs = get_regex_value(options.get_many("invert_filter"));

    let terminal_width: usize = match options.get_one::<usize>("width") {
        Some(&val) => val,
        None => get_width_of_terminal(),
    };

    let depth = config.get_depth(&options);

    // If depth is set, then we set the default number_of_lines to be max
    // instead of screen height

    let number_of_lines = match options.get_one::<usize>("number_of_lines") {
        Some(&val) => val,
        None => {
            if depth != usize::MAX {
                usize::MAX
            } else {
                get_height_of_terminal()
            }
        }
    };

    let is_colors = should_init_color(
        config.get_no_colors(&options),
        config.get_force_colors(&options),
    );

    let ignore_directories = match options.get_many::<String>("ignore_directory") {
        Some(values) => values
            .map(|v| v.as_str())
            .map(PathBuf::from)
            .collect::<Vec<PathBuf>>(),
        None => vec![],
    };

    let ignore_from_file_result = match options.get_one::<String>("ignore_all_in_file") {
        Some(val) => read_to_string(val)
            .unwrap()
            .lines()
            .map(Regex::new)
            .collect::<Vec<Result<Regex, Error>>>(),
        None => vec![],
    };
    let ignore_from_file = ignore_from_file_result
        .into_iter()
        .filter_map(|x| x.ok())
        .collect::<Vec<Regex>>();

    let invert_filter_regexs = invert_filter_regexs
        .into_iter()
        .chain(ignore_from_file)
        .collect::<Vec<Regex>>();

    let by_filecount = options.get_flag("by_filecount");
    let limit_filesystem = options.get_flag("limit_filesystem");
    let follow_links = options.get_flag("dereference_links");

    let allowed_filesystems = limit_filesystem
        .then(|| get_filesystem_devices(&target_dirs))
        .unwrap_or_default();
    let simplified_dirs = simplify_dir_names(&target_dirs);

    let ignored_full_path: HashSet<PathBuf> = ignore_directories
        .into_iter()
        .flat_map(|x| simplified_dirs.iter().map(move |d| d.join(&x)))
        .collect();

    let output_format = config.get_output_format(&options);

    let ignore_hidden = config.get_ignore_hidden(&options);

    let mut indicator = PIndicator::build_me();
    if !config.get_disable_progress(&options) {
        indicator.spawn(output_format.clone())
    }

    let filter_modified_time = config.get_modified_time_operator(&options);
    let filter_accessed_time = config.get_accessed_time_operator(&options);
    let filter_changed_time = config.get_created_time_operator(&options);

    let walk_data = WalkData {
        ignore_directories: ignored_full_path,
        filter_regex: &filter_regexs,
        invert_filter_regex: &invert_filter_regexs,
        allowed_filesystems,
        filter_modified_time,
        filter_accessed_time,
        filter_changed_time,
        use_apparent_size: config.get_apparent_size(&options),
        by_filecount,
        ignore_hidden,
        follow_links,
        progress_data: indicator.data.clone(),
        errors: errors_for_rayon,
    };
    let threads_to_use = config.get_threads(&options);
    let stack_size = config.get_custom_stack_size(&options);
    init_rayon(&stack_size, &threads_to_use);

    let top_level_nodes = walk_it(simplified_dirs, &walk_data);

    let tree = match summarize_file_types {
        true => get_all_file_types(&top_level_nodes, number_of_lines),
        false => {
            let agg_data = AggregateData {
                min_size: config.get_min_size(&options),
                only_dir: config.get_only_dir(&options),
                only_file: config.get_only_file(&options),
                number_of_lines,
                depth,
                using_a_filter: !filter_regexs.is_empty() || !invert_filter_regexs.is_empty(),
            };
            get_biggest(top_level_nodes, agg_data)
        }
    };

    // Must have stopped indicator before we print to stderr
    indicator.stop();

    if errors_final.lock().unwrap().abort {
        return;
    }

    let final_errors = walk_data.errors.lock().unwrap();
    if !final_errors.file_not_found.is_empty() {
        let err = final_errors
            .file_not_found
            .iter()
            .map(|a| a.as_ref())
            .collect::<Vec<&str>>()
            .join(", ");
        eprintln!("No such file or directory: {}", err);
    }
    if !final_errors.no_permissions.is_empty() {
        if config.get_print_errors(&options) {
            let err = final_errors
                .no_permissions
                .iter()
                .map(|a| a.as_ref())
                .collect::<Vec<&str>>()
                .join(", ");
            eprintln!("Did not have permissions for directories: {}", err);
        } else {
            eprintln!(
                "Did not have permissions for all directories (add --print-errors to see errors)"
            );
        }
    }
    if !final_errors.unknown_error.is_empty() {
        let err = final_errors
            .unknown_error
            .iter()
            .map(|a| a.as_ref())
            .collect::<Vec<&str>>()
            .join(", ");
        eprintln!("Unknown Error: {}", err);
    }

    if let Some(root_node) = tree {
        let idd = InitialDisplayData {
            short_paths: !config.get_full_paths(&options),
            is_reversed: !config.get_reverse(&options),
            colors_on: is_colors,
            by_filecount,
            is_screen_reader: config.get_screen_reader(&options),
            output_format,
            bars_on_right: config.get_bars_on_right(&options),
        };

        if config.get_output_json(&options) {
            println!("{}", serde_json::to_string(&root_node).unwrap());
        } else {
            draw_it(
                idd,
                config.get_no_bars(&options),
                terminal_width,
                &root_node,
                config.get_skip_total(&options),
            )
        }
    }
}

fn init_rayon(stack_size: &Option<usize>, threads: &Option<usize>) {
    // Rayon seems to raise this error on 32-bit builds
    // The global thread pool has not been initialized.: ThreadPoolBuildError { kind: GlobalPoolAlreadyInitialized }
    if cfg!(target_pointer_width = "64") {
        let result = panic::catch_unwind(|| build_thread_pool(*stack_size, *threads));
        if result.is_err() {
            eprintln!("Problem initializing rayon, try: export RAYON_NUM_THREADS=1")
        }
    }
}

fn build_thread_pool(
    stack: Option<usize>,
    threads: Option<usize>,
) -> Result<(), rayon::ThreadPoolBuildError> {
    let mut pool = rayon::ThreadPoolBuilder::new();

    if let Some(thread_count) = threads {
        pool = pool.num_threads(thread_count);
    }

    let stack_size = match stack {
        Some(s) => Some(s),
        None => {
            let large_stack = usize::pow(1024, 3);
            let mut s = System::new();
            s.refresh_memory();
            // Larger stack size if possible to handle cases with lots of nested directories
            let available = s.available_memory();
            if available > large_stack.try_into().unwrap() {
                Some(large_stack)
            } else {
                None
            }
        }
    };
    if let Some(stack_size_param) = stack_size {
        pool = pool.stack_size(stack_size_param);
    }
    pool.build_global()
}
0707010000001C000081A400000000000000000000000166980F0000000947000000000000000000000000000000000000001700000000dust-1.1.1/src/node.rsuse crate::dir_walker::WalkData;
use crate::platform::get_metadata;
use crate::utils::is_filtered_out_due_to_file_time;
use crate::utils::is_filtered_out_due_to_invert_regex;
use crate::utils::is_filtered_out_due_to_regex;

use std::cmp::Ordering;
use std::path::PathBuf;

#[derive(Debug, Eq, Clone)]
pub struct Node {
    pub name: PathBuf,
    pub size: u64,
    pub children: Vec<Node>,
    pub inode_device: Option<(u64, u64)>,
    pub depth: usize,
}

#[allow(clippy::too_many_arguments)]
pub fn build_node(
    dir: PathBuf,
    children: Vec<Node>,
    is_symlink: bool,
    is_file: bool,
    depth: usize,
    walk_data: &WalkData,
) -> Option<Node> {
    let use_apparent_size = walk_data.use_apparent_size;
    let by_filecount = walk_data.by_filecount;

    get_metadata(&dir, use_apparent_size).map(|data| {
        let inode_device = if is_symlink && !use_apparent_size {
            None
        } else {
            data.1
        };

        let size = if is_filtered_out_due_to_regex(walk_data.filter_regex, &dir)
            || is_filtered_out_due_to_invert_regex(walk_data.invert_filter_regex, &dir)
            || (is_symlink && !use_apparent_size)
            || by_filecount && !is_file
            || [
                (&walk_data.filter_modified_time, data.2 .0),
                (&walk_data.filter_accessed_time, data.2 .1),
                (&walk_data.filter_changed_time, data.2 .2),
            ]
            .iter()
            .any(|(filter_time, actual_time)| {
                is_filtered_out_due_to_file_time(filter_time, *actual_time)
            }) {
            0
        } else if by_filecount {
            1
        } else {
            data.0
        };

        Node {
            name: dir,
            size,
            children,
            inode_device,
            depth,
        }
    })
}

impl PartialEq for Node {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name && self.size == other.size && self.children == other.children
    }
}

impl Ord for Node {
    fn cmp(&self, other: &Self) -> Ordering {
        self.size
            .cmp(&other.size)
            .then_with(|| self.name.cmp(&other.name))
            .then_with(|| self.children.cmp(&other.children))
    }
}

impl PartialOrd for Node {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
0707010000001D000081A400000000000000000000000166980F0000001D42000000000000000000000000000000000000001B00000000dust-1.1.1/src/platform.rs#[allow(unused_imports)]
use std::fs;

use std::path::Path;

#[cfg(target_family = "unix")]
fn get_block_size() -> u64 {
    // All os specific implementations of MetadataExt seem to define a block as 512 bytes
    // https://doc.rust-lang.org/std/os/linux/fs/trait.MetadataExt.html#tymethod.st_blocks
    512
}

type InodeAndDevice = (u64, u64);
type FileTime = (i64, i64, i64);

#[cfg(target_family = "unix")]
pub fn get_metadata<P: AsRef<Path>>(
    path: P,
    use_apparent_size: bool,
) -> Option<(u64, Option<InodeAndDevice>, FileTime)> {
    use std::os::unix::fs::MetadataExt;
    match path.as_ref().metadata() {
        Ok(md) => {
            if use_apparent_size {
                Some((
                    md.len(),
                    Some((md.ino(), md.dev())),
                    (md.mtime(), md.atime(), md.ctime()),
                ))
            } else {
                Some((
                    md.blocks() * get_block_size(),
                    Some((md.ino(), md.dev())),
                    (md.mtime(), md.atime(), md.ctime()),
                ))
            }
        }
        Err(_e) => None,
    }
}

#[cfg(target_family = "windows")]
pub fn get_metadata<P: AsRef<Path>>(
    path: P,
    use_apparent_size: bool,
) -> Option<(u64, Option<InodeAndDevice>, FileTime)> {
    // On windows opening the file to get size, file ID and volume can be very
    // expensive because 1) it causes a few system calls, and more importantly 2) it can cause
    // windows defender to scan the file.
    // Therefore we try to avoid doing that for common cases, mainly those of
    // plain files:

    // The idea is to make do with the file size that we get from the OS for
    // free as part of iterating a folder. Therefore we want to make sure that
    // it makes sense to use that free size information:

    // Volume boundaries:
    // The user can ask us not to cross volume boundaries. If the DirEntry is a
    // plain file and not a reparse point or other non-trivial stuff, we assume
    // that the file is located on the same volume as the directory that
    // contains it.

    // File ID:
    // This optimization does deprive us of access to a file ID. As a
    // workaround, we just make one up that hopefully does not collide with real
    // file IDs.
    // Hard links: Unresolved. We don't get inode/file index, so hard links
    // count once for each link. Hopefully they are not too commonly in use on
    // windows.

    // Size:
    // We assume (naively?) that for the common cases the free size info is the
    // same as one would get by doing the expensive thing. Sparse, encrypted and
    // compressed files are not included in the common cases, as one can image
    // there being more than view on their size.

    // Savings in orders of magnitude in terms of time, io and cpu have been
    // observed on hdd, windows 10, some 100Ks files taking up some hundreds of
    // GBs:
    // Consistently opening the file: 30 minutes.
    // With this optimization:         8 sec.

    use std::io;
    use winapi_util::Handle;
    fn handle_from_path_limited(path: &Path) -> 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(
        path: &Path,
        use_apparent_size: bool,
    ) -> Option<(u64, Option<InodeAndDevice>, FileTime)> {
        use winapi_util::file::information;

        let h = handle_from_path_limited(path).ok()?;
        let info = information(&h).ok()?;

        if use_apparent_size {
            use filesize::PathExt;
            Some((
                path.size_on_disk().ok()?,
                Some((info.file_index(), info.volume_serial_number())),
                (
                    info.last_write_time().unwrap() as i64,
                    info.last_access_time().unwrap() as i64,
                    info.creation_time().unwrap() as i64,
                ),
            ))
        } else {
            Some((
                info.file_size(),
                Some((info.file_index(), info.volume_serial_number())),
                (
                    info.last_write_time().unwrap() as i64,
                    info.last_access_time().unwrap() as i64,
                    info.creation_time().unwrap() as i64,
                ),
            ))
        }
    }

    use std::os::windows::fs::MetadataExt;
    let path = path.as_ref();
    match path.metadata() {
        Ok(ref md) => {
            const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20;
            const FILE_ATTRIBUTE_READONLY: u32 = 0x01;
            const FILE_ATTRIBUTE_HIDDEN: u32 = 0x02;
            const FILE_ATTRIBUTE_SYSTEM: u32 = 0x04;
            const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
            const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
            const FILE_ATTRIBUTE_SPARSE_FILE: u32 = 0x00000200;
            const FILE_ATTRIBUTE_PINNED: u32 = 0x00080000;
            const FILE_ATTRIBUTE_UNPINNED: u32 = 0x00100000;
            const FILE_ATTRIBUTE_RECALL_ON_OPEN: u32 = 0x00040000;
            const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS: u32 = 0x00400000;
            const FILE_ATTRIBUTE_OFFLINE: u32 = 0x00001000;
            // normally FILE_ATTRIBUTE_SPARSE_FILE would be enough, however Windows sometimes likes to mask it out. see: https://stackoverflow.com/q/54560454
            const IS_PROBABLY_ONEDRIVE: u32 = FILE_ATTRIBUTE_SPARSE_FILE
                | FILE_ATTRIBUTE_PINNED
                | FILE_ATTRIBUTE_UNPINNED
                | FILE_ATTRIBUTE_RECALL_ON_OPEN
                | FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS
                | FILE_ATTRIBUTE_OFFLINE;
            let attr_filtered = md.file_attributes()
                & !(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
            if ((attr_filtered & FILE_ATTRIBUTE_ARCHIVE) != 0
                || (attr_filtered & FILE_ATTRIBUTE_DIRECTORY) != 0
                || md.file_attributes() == FILE_ATTRIBUTE_NORMAL)
                && !((attr_filtered & IS_PROBABLY_ONEDRIVE != 0) && use_apparent_size)
            {
                Some((
                    md.len(),
                    None,
                    (
                        md.last_write_time() as i64,
                        md.last_access_time() as i64,
                        md.creation_time() as i64,
                    ),
                ))
            } else {
                get_metadata_expensive(path, use_apparent_size)
            }
        }
        _ => get_metadata_expensive(path, use_apparent_size),
    }
}
0707010000001E000081A400000000000000000000000166980F00000012E2000000000000000000000000000000000000001B00000000dust-1.1.1/src/progress.rsuse std::{
    collections::HashSet,
    io::Write,
    path::Path,
    sync::{
        atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering},
        mpsc::{self, RecvTimeoutError, Sender},
        Arc, RwLock,
    },
    thread::JoinHandle,
    time::Duration,
};

use crate::display::human_readable_number;

/* -------------------------------------------------------------------------- */

pub const ORDERING: Ordering = Ordering::Relaxed;

const SPINNER_SLEEP_TIME: u64 = 100;
const PROGRESS_CHARS: [char; 4] = ['-', '\\', '|', '/'];
const PROGRESS_CHARS_LEN: usize = PROGRESS_CHARS.len();

pub trait ThreadSyncTrait<T> {
    fn set(&self, val: T);
    fn get(&self) -> T;
}

#[derive(Default)]
pub struct ThreadStringWrapper {
    inner: RwLock<String>,
}

impl ThreadSyncTrait<String> for ThreadStringWrapper {
    fn set(&self, val: String) {
        *self.inner.write().unwrap() = val;
    }

    fn get(&self) -> String {
        (*self.inner.read().unwrap()).clone()
    }
}

/* -------------------------------------------------------------------------- */

// creating an enum this way allows to have simpler syntax compared to a Mutex or a RwLock
#[allow(non_snake_case)]
pub mod Operation {
    pub const INDEXING: u8 = 0;
    pub const PREPARING: u8 = 1;
}

#[derive(Default)]
pub struct PAtomicInfo {
    pub num_files: AtomicUsize,
    pub total_file_size: AtomicU64,
    pub state: AtomicU8,
    pub current_path: ThreadStringWrapper,
}

impl PAtomicInfo {
    pub fn clear_state(&self, dir: &Path) {
        self.state.store(Operation::INDEXING, ORDERING);
        let dir_name = dir.to_string_lossy().to_string();
        self.current_path.set(dir_name);
        self.total_file_size.store(0, ORDERING);
        self.num_files.store(0, ORDERING);
    }
}

#[derive(Default)]
pub struct RuntimeErrors {
    pub no_permissions: HashSet<String>,
    pub file_not_found: HashSet<String>,
    pub unknown_error: HashSet<String>,
    pub abort: bool,
}

/* -------------------------------------------------------------------------- */

fn format_preparing_str(prog_char: char, data: &PAtomicInfo, output_display: &str) -> String {
    let path_in = data.current_path.get();
    let size = human_readable_number(data.total_file_size.load(ORDERING), output_display);
    format!("Preparing: {path_in} {size} ... {prog_char}")
}

fn format_indexing_str(prog_char: char, data: &PAtomicInfo, output_display: &str) -> String {
    let path_in = data.current_path.get();
    let file_count = data.num_files.load(ORDERING);
    let size = human_readable_number(data.total_file_size.load(ORDERING), output_display);
    let file_str = format!("{file_count} files, {size}");
    format!("Indexing: {path_in} {file_str} ... {prog_char}")
}

pub struct PIndicator {
    pub thread: Option<(Sender<()>, JoinHandle<()>)>,
    pub data: Arc<PAtomicInfo>,
}

impl PIndicator {
    pub fn build_me() -> Self {
        Self {
            thread: None,
            data: Arc::new(PAtomicInfo {
                ..Default::default()
            }),
        }
    }

    pub fn spawn(&mut self, output_display: String) {
        let data = self.data.clone();
        let (stop_handler, receiver) = mpsc::channel::<()>();

        let time_info_thread = std::thread::spawn(move || {
            let mut progress_char_i: usize = 0;
            let mut stdout = std::io::stdout();
            let mut msg = "".to_string();

            // While the timeout triggers we go round the loop
            // If we disconnect or the sender sends its message we exit the while loop
            while let Err(RecvTimeoutError::Timeout) =
                receiver.recv_timeout(Duration::from_millis(SPINNER_SLEEP_TIME))
            {
                // Clear the text written by 'write!'& Return at the start of line
                print!("\r{:width$}", " ", width = msg.len());
                let prog_char = PROGRESS_CHARS[progress_char_i];

                msg = match data.state.load(ORDERING) {
                    Operation::INDEXING => format_indexing_str(prog_char, &data, &output_display),
                    Operation::PREPARING => format_preparing_str(prog_char, &data, &output_display),
                    _ => panic!("Unknown State"),
                };

                write!(stdout, "\r{msg}").unwrap();
                stdout.flush().unwrap();

                progress_char_i += 1;
                progress_char_i %= PROGRESS_CHARS_LEN;
            }
            print!("\r{:width$}", " ", width = msg.len());
            print!("\r");
            stdout.flush().unwrap();
        });
        self.thread = Some((stop_handler, time_info_thread))
    }

    pub fn stop(self) {
        if let Some((stop_handler, thread)) = self.thread {
            stop_handler.send(()).unwrap();
            thread.join().unwrap();
        }
    }
}
0707010000001F000081A400000000000000000000000166980F0000001651000000000000000000000000000000000000001800000000dust-1.1.1/src/utils.rsuse platform::get_metadata;
use std::collections::HashSet;
use std::path::{Path, PathBuf};

use crate::config::DAY_SECONDS;

use crate::dir_walker::Operater;
use crate::platform;
use regex::Regex;

pub fn simplify_dir_names<P: AsRef<Path>>(dirs: &[P]) -> HashSet<PathBuf> {
    let mut top_level_names: HashSet<PathBuf> = HashSet::with_capacity(dirs.len());

    for t in dirs {
        let top_level_name = normalize_path(t);
        let mut can_add = true;
        let mut to_remove: Vec<PathBuf> = Vec::new();

        for tt in top_level_names.iter() {
            if is_a_parent_of(&top_level_name, tt) {
                to_remove.push(tt.to_path_buf());
            } else if is_a_parent_of(tt, &top_level_name) {
                can_add = false;
            }
        }
        for r in to_remove {
            top_level_names.remove(&r);
        }
        if can_add {
            top_level_names.insert(top_level_name);
        }
    }

    top_level_names
}

pub fn get_filesystem_devices<P: AsRef<Path>>(paths: &[P]) -> HashSet<u64> {
    // Gets the device ids for the filesystems which are used by the argument paths
    paths
        .iter()
        .filter_map(|p| match get_metadata(p, false) {
            Some((_size, Some((_id, dev)), _time)) => Some(dev),
            _ => None,
        })
        .collect()
}

pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
    // normalize path ...
    // 1. removing repeated separators
    // 2. removing interior '.' ("current directory") path segments
    // 3. removing trailing extra separators and '.' ("current directory") path segments
    // * `Path.components()` does all the above work; ref: <https://doc.rust-lang.org/std/path/struct.Path.html#method.components>
    // 4. changing to os preferred separator (automatically done by recollecting components back into a PathBuf)
    path.as_ref().components().collect()
}

pub fn is_filtered_out_due_to_regex(filter_regex: &[Regex], dir: &Path) -> bool {
    if filter_regex.is_empty() {
        false
    } else {
        filter_regex
            .iter()
            .all(|f| !f.is_match(&dir.as_os_str().to_string_lossy()))
    }
}

pub fn is_filtered_out_due_to_file_time(
    filter_time: &Option<(Operater, i64)>,
    actual_time: i64,
) -> bool {
    match filter_time {
        None => false,
        Some((Operater::Equal, bound_time)) => {
            !(actual_time >= *bound_time && actual_time < *bound_time + DAY_SECONDS)
        }
        Some((Operater::GreaterThan, bound_time)) => actual_time < *bound_time,
        Some((Operater::LessThan, bound_time)) => actual_time > *bound_time,
    }
}

pub fn is_filtered_out_due_to_invert_regex(filter_regex: &[Regex], dir: &Path) -> bool {
    filter_regex
        .iter()
        .any(|f| f.is_match(&dir.as_os_str().to_string_lossy()))
}

fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
    let parent = parent.as_ref();
    let child = child.as_ref();
    child.starts_with(parent) && !parent.starts_with(child)
}

mod tests {
    #[allow(unused_imports)]
    use super::*;

    #[test]
    fn test_simplify_dir() {
        let mut correct = HashSet::new();
        correct.insert(PathBuf::from("a"));
        assert_eq!(simplify_dir_names(&["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(&["a/b/c", "a/b", "a/b/d/f"]), correct);
        assert_eq!(simplify_dir_names(&["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(&[
                "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(&["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(&["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(&["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("/", "/"));
    }
}
07070100000020000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001100000000dust-1.1.1/tests07070100000021000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001A00000000dust-1.1.1/tests/test_dir07070100000022000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001F00000000dust-1.1.1/tests/test_dir/many07070100000023000081A400000000000000000000000166980F0000000000000000000000000000000000000000000000002600000000dust-1.1.1/tests/test_dir/many/a_file07070100000024000081A400000000000000000000000166980F0000000006000000000000000000000000000000000000002A00000000dust-1.1.1/tests/test_dir/many/hello_filehello
07070100000025000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001B00000000dust-1.1.1/tests/test_dir207070100000026000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000001F00000000dust-1.1.1/tests/test_dir2/dir07070100000027000081A400000000000000000000000166980F0000000005000000000000000000000000000000000000002500000000dust-1.1.1/tests/test_dir2/dir/hellohello07070100000028000081A400000000000000000000000166980F0000000005000000000000000000000000000000000000002A00000000dust-1.1.1/tests/test_dir2/dir_name_clashhello07070100000029000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000002900000000dust-1.1.1/tests/test_dir2/dir_substring0707010000002A000081A400000000000000000000000166980F0000000006000000000000000000000000000000000000002F00000000dust-1.1.1/tests/test_dir2/dir_substring/hellohello
0707010000002B000081A400000000000000000000000166980F0000000000000000000000000000000000000000000000007B00000000dust-1.1.1/tests/test_dir2/long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes_over_80_characters_i_wonder0707010000002C000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000002900000000dust-1.1.1/tests/test_dir_hidden_entries0707010000002D000081A400000000000000000000000166980F0000000012000000000000000000000000000000000000003600000000dust-1.1.1/tests/test_dir_hidden_entries/.hidden_filesomething
.secret
0707010000002E000081A400000000000000000000000166980F0000000000000000000000000000000000000000000000003100000000dust-1.1.1/tests/test_dir_hidden_entries/.secret0707010000002F000041ED00000000000000000000000266980F0000000000000000000000000000000000000000000000002200000000dust-1.1.1/tests/test_dir_unicode07070100000030000081A400000000000000000000000166980F0000000000000000000000000000000000000000000000004700000000dust-1.1.1/tests/test_dir_unicode/γƒ©γ‚¦γƒˆγ―ι›£γ—γ„γ§γ™οΌ.japan07070100000031000081A400000000000000000000000166980F0000000000000000000000000000000000000000000000002F00000000dust-1.1.1/tests/test_dir_unicode/πŸ‘©.unicode07070100000032000081A400000000000000000000000166980F000000287D000000000000000000000000000000000000002600000000dust-1.1.1/tests/test_exact_output.rsuse assert_cmd::Command;
use std::ffi::OsStr;
use std::process::Output;
use std::sync::Once;
use std::{io, str};

static INIT: Once = Once::new();
static UNREADABLE_DIR_PATH: &str = "/tmp/unreadable_dir";

/**
 * This file contains tests that verify the exact output of the command.
 * This output differs on Linux / Mac so the tests are harder to write and debug
 * Windows is ignored here because the results vary by host making exact testing impractical
 *
 * Despite the above problems, these tests are good as they are the closest to 'the real thing'.
 */

//  Warning: File sizes differ on both platform and on the format of the disk.
/// Copy to /tmp dir - we assume that the formatting of the /tmp partition
/// is consistent. If the tests fail your /tmp filesystem probably differs
fn copy_test_data(dir: &str) {
    // First remove the existing directory - just in case it is there and has incorrect data
    let last_slash = dir.rfind('/').unwrap();
    let last_part_of_dir = dir.chars().skip(last_slash).collect::<String>();
    let _ = Command::new("rm")
        .arg("-rf")
        .arg("/tmp/".to_owned() + &*last_part_of_dir)
        .ok();

    let _ = Command::new("cp")
        .arg("-r")
        .arg(dir)
        .arg("/tmp/")
        .ok()
        .map_err(|err| eprintln!("Error copying directory for test setup\n{:?}", err));
}

fn create_unreadable_directory() -> io::Result<()> {
    #[cfg(unix)]
    {
        use std::fs;
        use std::fs::Permissions;
        use std::os::unix::fs::PermissionsExt;
        fs::create_dir_all(UNREADABLE_DIR_PATH)?;
        fs::set_permissions(UNREADABLE_DIR_PATH, Permissions::from_mode(0))?;
    }
    Ok(())
}

fn initialize() {
    INIT.call_once(|| {
        copy_test_data("tests/test_dir");
        copy_test_data("tests/test_dir2");
        copy_test_data("tests/test_dir_unicode");

        if let Err(e) = create_unreadable_directory() {
            panic!("Failed to create unreadable directory: {}", e);
        }
    });
}

fn run_cmd<T: AsRef<OsStr>>(command_args: &[T]) -> Output {
    initialize();
    let mut to_run = &mut Command::cargo_bin("dust").unwrap();
    for p in command_args {
        to_run = to_run.arg(p);
    }
    to_run.unwrap()
}

fn exact_stdout_test<T: AsRef<OsStr>>(command_args: &[T], valid_stdout: Vec<String>) {
    let to_run = run_cmd(command_args);

    let stdout_output = str::from_utf8(&to_run.stdout).unwrap().to_owned();
    let will_fail = valid_stdout.iter().any(|i| stdout_output.contains(i));
    if !will_fail {
        eprintln!(
            "output(stdout):\n{}\ndoes not contain any of:\n{}",
            stdout_output,
            valid_stdout.join("\n\n")
        );
    }
    assert!(will_fail);
}

fn exact_stderr_test<T: AsRef<OsStr>>(command_args: &[T], valid_stderr: String) {
    let to_run = run_cmd(command_args);

    let stderr_output = str::from_utf8(&to_run.stderr).unwrap().trim();
    assert_eq!(stderr_output, valid_stderr);
}

// "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_basic() {
    // -c is no color mode - This makes testing much simpler
    exact_stdout_test(&["-c", "-B", "/tmp/test_dir/"], main_output());
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_multi_arg() {
    let command_args = [
        "-c",
        "-B",
        "/tmp/test_dir/many/",
        "/tmp/test_dir",
        "/tmp/test_dir",
    ];
    exact_stdout_test(&command_args, main_output());
}

fn main_output() -> Vec<String> {
    // Some linux currently thought to be Manjaro, Arch
    // Although probably depends on how drive is formatted
    let mac_and_some_linux = r#"
  0B     β”Œβ”€β”€ a_file    β”‚β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–ˆ β”‚   0%
4.0K     β”œβ”€β”€ hello_fileβ”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
4.0K   β”Œβ”€β”΄ many        β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
4.0K β”Œβ”€β”΄ test_dir      β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
"#
    .trim()
    .to_string();

    let ubuntu = r#"
  0B     β”Œβ”€β”€ a_file    β”‚                β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–ˆ β”‚   0%
4.0K     β”œβ”€β”€ hello_fileβ”‚                β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚  33%
8.0K   β”Œβ”€β”΄ many        β”‚                β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚  67%
 12K β”Œβ”€β”΄ test_dir      β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
  "#
    .trim()
    .to_string();

    vec![mac_and_some_linux, ubuntu]
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_main_long_paths() {
    let command_args = ["-c", "-p", "-B", "/tmp/test_dir/"];
    exact_stdout_test(&command_args, main_output_long_paths());
}

fn main_output_long_paths() -> Vec<String> {
    let mac_and_some_linux = r#"
  0B     β”Œβ”€β”€ /tmp/test_dir/many/a_file    β”‚β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–ˆ β”‚   0%
4.0K     β”œβ”€β”€ /tmp/test_dir/many/hello_fileβ”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
4.0K   β”Œβ”€β”΄ /tmp/test_dir/many             β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
4.0K β”Œβ”€β”΄ /tmp/test_dir                    β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
"#
    .trim()
    .to_string();
    let ubuntu = r#"
  0B     β”Œβ”€β”€ /tmp/test_dir/many/a_file    β”‚         β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–ˆ β”‚   0%
4.0K     β”œβ”€β”€ /tmp/test_dir/many/hello_fileβ”‚         β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚  33%
8.0K   β”Œβ”€β”΄ /tmp/test_dir/many             β”‚         β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚  67%
 12K β”Œβ”€β”΄ /tmp/test_dir                    β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
"#
    .trim()
    .to_string();
    vec![mac_and_some_linux, ubuntu]
}

// Check against directories and files whose names are substrings of each other
#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_substring_of_names_and_long_names() {
    let command_args = ["-c", "-B", "/tmp/test_dir2"];
    exact_stdout_test(&command_args, no_substring_of_names_output());
}

fn no_substring_of_names_output() -> Vec<String> {
    let ubuntu = "
  0B   β”Œβ”€β”€ long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes..
4.0K   β”œβ”€β”€ dir_name_clash
4.0K   β”‚ β”Œβ”€β”€ hello
8.0K   β”œβ”€β”΄ dir
4.0K   β”‚ β”Œβ”€β”€ hello
8.0K   β”œβ”€β”΄ dir_substring
 24K β”Œβ”€β”΄ test_dir2
    "
    .trim()
    .into();

    let mac_and_some_linux = "
  0B   β”Œβ”€β”€ long_dir_name_what_a_very_long_dir_name_what_happens_when_this_goes..
4.0K   β”‚ β”Œβ”€β”€ hello
4.0K   β”œβ”€β”΄ dir
4.0K   β”œβ”€β”€ dir_name_clash
4.0K   β”‚ β”Œβ”€β”€ hello
4.0K   β”œβ”€β”΄ dir_substring
 12K β”Œβ”€β”΄ test_dir2
  "
    .trim()
    .into();
    vec![mac_and_some_linux, ubuntu]
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_unicode_directories() {
    let command_args = ["-c", "-B", "/tmp/test_dir_unicode"];
    exact_stdout_test(&command_args, unicode_dir());
}

fn unicode_dir() -> Vec<String> {
    // The way unicode & asian characters are rendered on the terminal should make this line up
    let ubuntu = "
  0B   β”Œβ”€β”€ γƒ©γ‚¦γƒˆγ―ι›£γ—γ„γ§γ™οΌ.japanβ”‚                                  β–ˆ β”‚   0%
  0B   β”œβ”€β”€ πŸ‘©.unicode                β”‚                                  β–ˆ β”‚   0%
4.0K β”Œβ”€β”΄ test_dir_unicode            β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ 100%
    "
    .trim()
    .into();

    let mac_and_some_linux = "
0B   β”Œβ”€β”€ γƒ©γ‚¦γƒˆγ―ι›£γ—γ„γ§γ™οΌ.japanβ”‚                                    β–ˆ β”‚   0%
0B   β”œβ”€β”€ πŸ‘©.unicode                β”‚                                    β–ˆ β”‚   0%
0B β”Œβ”€β”΄ test_dir_unicode            β”‚                                    β–ˆ β”‚   0%
    "
    .trim()
    .into();
    vec![mac_and_some_linux, ubuntu]
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_apparent_size() {
    let command_args = ["-c", "-s", "-b", "/tmp/test_dir"];
    exact_stdout_test(&command_args, apparent_size_output());
}

fn apparent_size_output() -> Vec<String> {
    // The apparent directory sizes are too unpredictable and system dependent to try and match
    let one_space_before = r#"
 0B     β”Œβ”€β”€ a_file
 6B     β”œβ”€β”€ hello_file
 "#
    .trim()
    .to_string();

    let two_space_before = r#"
  0B     β”Œβ”€β”€ a_file
  6B     β”œβ”€β”€ hello_file
 "#
    .trim()
    .to_string();

    vec![one_space_before, two_space_before]
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_permission_normal() {
    let command_args = [UNREADABLE_DIR_PATH];
    let permission_msg =
        r#"Did not have permissions for all directories (add --print-errors to see errors)"#
            .trim()
            .to_string();
    exact_stderr_test(&command_args, permission_msg);
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_permission_flag() {
    // add the flag to CLI
    let command_args = ["--print-errors", UNREADABLE_DIR_PATH];
    let permission_msg = format!(
        "Did not have permissions for directories: {}",
        UNREADABLE_DIR_PATH
    );
    exact_stderr_test(&command_args, permission_msg);
}
07070100000033000081A400000000000000000000000166980F0000002001000000000000000000000000000000000000001F00000000dust-1.1.1/tests/test_flags.rsuse assert_cmd::Command;
use std::ffi::OsStr;
use std::str;

/**
 * This file contains tests that test a substring of the output using '.contains'
 *
 * These tests should be the same cross platform
 */

fn build_command<T: AsRef<OsStr>>(command_args: Vec<T>) -> String {
    let mut cmd = &mut Command::cargo_bin("dust").unwrap();
    for p in command_args {
        cmd = cmd.arg(p);
    }
    let finished = &cmd.unwrap();
    let stderr = str::from_utf8(&finished.stderr).unwrap();
    assert_eq!(stderr, "");

    str::from_utf8(&finished.stdout).unwrap().into()
}

// We can at least test the file names are there
#[test]
pub fn test_basic_output() {
    let output = build_command(vec!["tests/test_dir/"]);

    assert!(output.contains(" β”Œβ”€β”΄ "));
    assert!(output.contains("test_dir "));
    assert!(output.contains("  β”Œβ”€β”΄ "));
    assert!(output.contains("many "));
    assert!(output.contains("    β”œβ”€β”€ "));
    assert!(output.contains("hello_file"));
    assert!(output.contains("     β”Œβ”€β”€ "));
    assert!(output.contains("a_file "));
}

#[test]
pub fn test_output_no_bars_means_no_excess_spaces() {
    let output = build_command(vec!["-b", "tests/test_dir/"]);
    // If bars are not being shown we don't need to pad the output with spaces
    assert!(output.contains("many"));
    assert!(!output.contains("many    "));
}

#[test]
pub fn test_reverse_flag() {
    let output = build_command(vec!["-r", "-c", "tests/test_dir/"]);
    assert!(output.contains(" └─┬ test_dir "));
    assert!(output.contains("  └─┬ many "));
    assert!(output.contains("    β”œβ”€β”€ hello_file"));
    assert!(output.contains("    └── a_file "));
}

#[test]
pub fn test_d_flag_works() {
    // We should see the top level directory but not the sub dirs / files:
    let output = build_command(vec!["-d", "1", "tests/test_dir/"]);
    assert!(!output.contains("hello_file"));
}

#[test]
pub fn test_threads_flag_works() {
    let output = build_command(vec!["-T", "1", "tests/test_dir/"]);
    assert!(output.contains("hello_file"));
}

#[test]
pub fn test_d_flag_works_and_still_recurses_down() {
    // We had a bug where running with '-d 1' would stop at the first directory and the code
    // would fail to recurse down
    let output = build_command(vec!["-d", "1", "-f", "-c", "tests/test_dir2/"]);
    assert!(output.contains("1   β”Œβ”€β”€ dir"));
    assert!(output.contains("4 β”Œβ”€β”΄ test_dir2"));
}

// Check against directories and files whose names are substrings of each other
#[test]
pub fn test_ignore_dir() {
    let output = build_command(vec!["-c", "-X", "dir_substring", "tests/test_dir2/"]);
    assert!(!output.contains("dir_substring"));
}

#[test]
pub fn test_ignore_all_in_file() {
    let output = build_command(vec![
        "-c",
        "-I",
        "tests/test_dir_hidden_entries/.hidden_file",
        "tests/test_dir_hidden_entries/",
    ]);
    assert!(output.contains(" test_dir_hidden_entries"));
    assert!(!output.contains(".secret"));
}

#[test]
pub fn test_with_bad_param() {
    let mut cmd = Command::cargo_bin("dust").unwrap();
    let result = cmd.arg("bad_place").unwrap();
    let stderr = str::from_utf8(&result.stderr).unwrap();
    assert!(stderr.contains("No such file or directory"));
}

#[test]
pub fn test_hidden_flag() {
    // Check we can see the hidden file normally
    let output = build_command(vec!["-c", "tests/test_dir_hidden_entries/"]);
    assert!(output.contains(".hidden_file"));
    assert!(output.contains("β”Œβ”€β”΄ test_dir_hidden_entries"));

    // Check that adding the '-h' flag causes us to not see hidden files
    let output = build_command(vec!["-c", "-i", "tests/test_dir_hidden_entries/"]);
    assert!(!output.contains(".hidden_file"));
    assert!(output.contains("β”Œβ”€β”€ test_dir_hidden_entries"));
}

#[test]
pub fn test_number_of_files() {
    // Check we can see the hidden file normally
    let output = build_command(vec!["-c", "-f", "tests/test_dir"]);
    assert!(output.contains("1     β”Œβ”€β”€ a_file "));
    assert!(output.contains("1     β”œβ”€β”€ hello_file"));
    assert!(output.contains("2   β”Œβ”€β”΄ many"));
    assert!(output.contains("2 β”Œβ”€β”΄ test_dir"));
}

#[test]
pub fn test_show_files_by_type() {
    // Check we can list files by type
    let output = build_command(vec!["-c", "-t", "tests"]);
    assert!(output.contains(" .unicode"));
    assert!(output.contains(" .japan"));
    assert!(output.contains(" .rs"));
    assert!(output.contains(" (no extension)"));
    assert!(output.contains("β”Œβ”€β”΄ (total)"));
}

#[test]
#[cfg(target_family = "unix")]
pub fn test_show_files_only() {
    let output = build_command(vec!["-c", "-F", "tests/test_dir"]);
    assert!(output.contains("a_file"));
    assert!(output.contains("hello_file"));
    assert!(!output.contains("many"));
}

#[test]
pub fn test_output_skip_total() {
    let output = build_command(vec![
        "--skip-total",
        "tests/test_dir/many/hello_file",
        "tests/test_dir/many/a_file",
    ]);
    assert!(output.contains("hello_file"));
    assert!(!output.contains("(total)"));
}

#[test]
pub fn test_output_screen_reader() {
    let output = build_command(vec!["--screen-reader", "-c", "tests/test_dir/"]);
    println!("{}", output);
    assert!(output.contains("test_dir   0"));
    assert!(output.contains("many       1"));
    assert!(output.contains("hello_file 2"));
    assert!(output.contains("a_file     2"));

    // Verify no 'symbols' reported by screen reader
    assert!(!output.contains('β”‚'));

    for block in ['β–ˆ', 'β–“', 'β–’', 'β–‘'] {
        assert!(!output.contains(block));
    }
}

#[test]
pub fn test_show_files_by_regex_match_lots() {
    // Check we can see '.rs' files in the tests directory
    let output = build_command(vec!["-c", "-e", "\\.rs$", "tests"]);
    assert!(output.contains(" β”Œβ”€β”΄ tests"));
    assert!(!output.contains("0B β”Œβ”€β”€ tests"));
    assert!(!output.contains("0B β”Œβ”€β”΄ tests"));
}

#[test]
pub fn test_show_files_by_regex_match_nothing() {
    // Check there are no files named: '.match_nothing' in the tests directory
    let output = build_command(vec!["-c", "-e", "match_nothing$", "tests"]);
    assert!(output.contains("0B β”Œβ”€β”€ tests"));
}

#[test]
pub fn test_show_files_by_regex_match_multiple() {
    let output = build_command(vec![
        "-c",
        "-e",
        "test_dir_hidden",
        "-e",
        "test_dir2",
        "-n",
        "100",
        "tests",
    ]);
    assert!(output.contains("test_dir2"));
    assert!(output.contains("test_dir_hidden"));
    assert!(!output.contains("many")); // We do not find the 'many' folder in the 'test_dir' folder
}

#[test]
pub fn test_show_files_by_invert_regex() {
    let output = build_command(vec!["-c", "-f", "-v", "e", "tests/test_dir2"]);
    // There are 0 files without 'e' in the name
    assert!(output.contains("0 β”Œβ”€β”€ test_dir2"));

    let output = build_command(vec!["-c", "-f", "-v", "a", "tests/test_dir2"]);
    // There are 2 files without 'a' in the name
    assert!(output.contains("2 β”Œβ”€β”΄ test_dir2"));

    // There are 4 files in the test_dir2 hierarchy
    let output = build_command(vec!["-c", "-f", "-v", "match_nothing$", "tests/test_dir2"]);
    assert!(output.contains("4 β”Œβ”€β”΄ test_dir2"));
}

#[test]
pub fn test_show_files_by_invert_regex_match_multiple() {
    // We ignore test_dir2 & test_dir_unicode, leaving the test_dir folder
    // which has the 'many' folder inside
    let output = build_command(vec![
        "-c",
        "-v",
        "test_dir2",
        "-v",
        "test_dir_unicode",
        "-n",
        "100",
        "tests",
    ]);
    assert!(!output.contains("test_dir2"));
    assert!(!output.contains("test_dir_unicode"));
    assert!(output.contains("many"));
}

#[test]
pub fn test_no_color() {
    let output = build_command(vec!["-c"]);
    // Red is 31
    assert!(!output.contains("\x1B[31m"));
    assert!(!output.contains("\x1B[0m"));
}

#[test]
pub fn test_force_color() {
    let output = build_command(vec!["-C"]);
    // Red is 31
    assert!(output.contains("\x1B[31m"));
    assert!(output.contains("\x1B[0m"));
}
07070100000034000081A400000000000000000000000166980F0000000001000000000000000000000000000000000000001A00000000dust-1.1.1/tests/tests.rs
07070100000035000081A400000000000000000000000166980F00000011E0000000000000000000000000000000000000002300000000dust-1.1.1/tests/tests_symlinks.rsuse assert_cmd::Command;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::str;

use tempfile::Builder;
use tempfile::TempDir;

// File sizes differ on both platform and on the format of the disk.
// Windows: `ln` is not usually an available command; creation of symbolic links requires special enhanced permissions

fn build_temp_file(dir: &TempDir) -> PathBuf {
    let file_path = dir.path().join("notes.txt");
    let mut file = File::create(&file_path).unwrap();
    writeln!(file, "I am a temp file").unwrap();
    file_path
}

fn link_it(link_path: PathBuf, file_path_s: &str, is_soft: bool) -> String {
    let link_name_s = link_path.to_str().unwrap();
    let mut c = Command::new("ln");
    if is_soft {
        c.arg("-s");
    }
    c.arg(file_path_s);
    c.arg(link_name_s);
    assert!(c.output().is_ok());
    link_name_s.into()
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_soft_sym_link() {
    let dir = Builder::new().tempdir().unwrap();
    let file = build_temp_file(&dir);
    let dir_s = dir.path().to_str().unwrap();
    let file_path_s = file.to_str().unwrap();

    let link_name = dir.path().join("the_link");
    let link_name_s = link_it(link_name, file_path_s, true);

    let c = format!(" β”œβ”€β”€ {}", link_name_s);
    let b = format!(" β”Œβ”€β”€ {}", file_path_s);
    let a = format!("─┴ {}", dir_s);

    let mut cmd = Command::cargo_bin("dust").unwrap();
    // Mac test runners create long filenames in tmp directories
    let output = cmd
        .args(["-p", "-c", "-s", "-w", "999", dir_s])
        .unwrap()
        .stdout;

    let output = str::from_utf8(&output).unwrap();

    assert!(output.contains(a.as_str()));
    assert!(output.contains(b.as_str()));
    assert!(output.contains(c.as_str()));
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_hard_sym_link() {
    let dir = Builder::new().tempdir().unwrap();
    let file = build_temp_file(&dir);
    let dir_s = dir.path().to_str().unwrap();
    let file_path_s = file.to_str().unwrap();

    let link_name = dir.path().join("the_link");
    link_it(link_name, file_path_s, false);

    let file_output = format!(" β”Œβ”€β”€ {}", file_path_s);
    let dirs_output = format!("─┴ {}", dir_s);

    let mut cmd = Command::cargo_bin("dust").unwrap();
    // Mac test runners create long filenames in tmp directories
    let output = cmd.args(["-p", "-c", "-w", "999", dir_s]).unwrap().stdout;

    // The link should not appear in the output because multiple inodes are now ordered
    // then filtered.
    let output = str::from_utf8(&output).unwrap();
    assert!(output.contains(dirs_output.as_str()));
    assert!(output.contains(file_output.as_str()));
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_hard_sym_link_no_dup_multi_arg() {
    let dir = Builder::new().tempdir().unwrap();
    let dir_link = Builder::new().tempdir().unwrap();
    let file = build_temp_file(&dir);
    let dir_s = dir.path().to_str().unwrap();
    let dir_link_s = dir_link.path().to_str().unwrap();
    let file_path_s = file.to_str().unwrap();

    let link_name = dir_link.path().join("the_link");
    let link_name_s = link_it(link_name, file_path_s, false);

    let mut cmd = Command::cargo_bin("dust").unwrap();

    // Mac test runners create long filenames in tmp directories
    let output = cmd
        .args(["-p", "-c", "-w", "999", "-b", dir_link_s, dir_s])
        .unwrap()
        .stdout;

    // The link or the file should appear but not both
    let output = str::from_utf8(&output).unwrap();
    let has_file_only = output.contains(file_path_s) && !output.contains(&link_name_s);
    let has_link_only = !output.contains(file_path_s) && output.contains(&link_name_s);
    assert!(has_file_only || has_link_only);
}

#[cfg_attr(target_os = "windows", ignore)]
#[test]
pub fn test_recursive_sym_link() {
    let dir = Builder::new().tempdir().unwrap();
    let dir_s = dir.path().to_str().unwrap();

    let link_name = dir.path().join("the_link");
    let link_name_s = link_it(link_name, dir_s, true);

    let a = format!("─┬ {}", dir_s);
    let b = format!(" └── {}", link_name_s);

    let mut cmd = Command::cargo_bin("dust").unwrap();
    let output = cmd
        .arg("-p")
        .arg("-c")
        .arg("-r")
        .arg("-s")
        .arg("-w")
        .arg("999")
        .arg(dir_s)
        .unwrap()
        .stdout;
    let output = str::from_utf8(&output).unwrap();

    assert!(output.contains(a.as_str()));
    assert!(output.contains(b.as_str()));
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!421 blocks
openSUSE Build Service is sponsored by