Merge pull request 'v0.2.0 - Performance & Distribution' (#1) from feat/optimization-refactor into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
/dist
|
||||||
|
|||||||
+40
-13
@@ -5,19 +5,46 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-01-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Comparison Mode**: Added `--compare` flag to display total size vs. non-ignored size (respecting `.gitignore`).
|
||||||
|
- **Unit Control**: Added `--units <binary|decimal>` flag to toggle standard IEC (MiB) vs. SI (MB) unit systems.
|
||||||
|
- **Precision Control**: Added `--precision <number>` to control decimal places in output (default: 2).
|
||||||
|
- **Physical vs. Logical Distinction**: Explicitly show **Apparent Size** (logical) and **Disk Usage** (actual blocks consumed) in all output formats.
|
||||||
|
- **Release Assets**:
|
||||||
|
- Project **Makefile** for easy system-wide installation via `make install`.
|
||||||
|
- Universal **`scripts/release.sh`** for generating optimized binaries, man pages, shell completions, and `.deb` packages.
|
||||||
|
- Official **man page** (`sized.1`) for standard Unix documentation documentation.
|
||||||
|
- Updated **Homebrew Formula** for tap-based distribution.
|
||||||
|
- **Improved Errors**: Graceful handling for inaccessible directories (e.g., system protected folders) with "Access Denied" status.
|
||||||
|
- Added a full user guide in `MANUAL.md`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Performance Overhaul**:
|
||||||
|
- Replaced serial/par-bridge traversal with `ignore::WalkParallel` for true multi-threaded directory scanning.
|
||||||
|
- Implemented **dynamic thread allocation** to balance CPU load between directory breadth and depth.
|
||||||
|
- Refactored core logic to a **single-pass, in-memory tree construction** algorithm ($O(N)$ complexity).
|
||||||
|
- **Architecture**: Refactored the project into a usable Rust library (`src/lib.rs`) and a thin CLI wrapper (`src/main.rs`).
|
||||||
|
- **Dependencies**:
|
||||||
|
- Switched to `byte-unit` for more flexible and accurate unit formatting.
|
||||||
|
- Replaced `walkdir` with `ignore` for faster traversal and native `.gitignore` support.
|
||||||
|
- Replaced `atty` and `chrono` with standard library features and the lightweight `time` crate.
|
||||||
|
- **Serialization**: Optimized JSON and CSV output for zero-copy performance and detailed metric reporting.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Resolved performance bottleneck in extremely deep directory structures.
|
||||||
|
- Fixed inconsistent total size reporting when `.gitignore` was active.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Dependency on `walkdir`, `humansize`, `chrono`, and `atty`.
|
||||||
|
|
||||||
## [0.1.0] - 2026-01-22
|
## [0.1.0] - 2026-01-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Core directory traversal and size calculation logic.
|
- Initial release with core directory traversal and size calculation.
|
||||||
- Parallel processing using `rayon` for high performance.
|
- Basic parallel processing using `rayon`.
|
||||||
- Rich table output format using `comfy-table` with colored entries.
|
- Rich table output via `comfy-table`.
|
||||||
- Recursive depth control via `-d`/`--depth`.
|
- Basic filtering (`--min-size`, `--depth`) and sorting (`--sort`).
|
||||||
- Minimum size filtering via `-m`/`--min-size`.
|
- Output format support: Text, CSV, and JSON.
|
||||||
- Multi-column sorting support (`--sort`).
|
- Save to file and shell completion generation.
|
||||||
- Relative path display by default, with `-f`/`--path-full` override.
|
|
||||||
- "Total size" summary with dual units (Binary/Decimal) and block counts.
|
|
||||||
- Concurrency control via `-j`/`--threads`.
|
|
||||||
- `.gitignore` and `.ignore` file support via `-i`/`--ignore`.
|
|
||||||
- Multiple output formats: Text (Table), CSV, JSON (`--format`).
|
|
||||||
- Save to file functionality with auto-naming (`--save`).
|
|
||||||
- Shell completion generation (`--completions`).
|
|
||||||
|
|||||||
Generated
+368
-142
@@ -8,7 +8,7 @@ version = "0.7.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.17",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
@@ -23,13 +23,10 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "anes"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
@@ -88,14 +85,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "assert_cmd"
|
||||||
version = "0.2.14"
|
version = "2.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"anstyle",
|
||||||
|
"bstr",
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"predicates",
|
||||||
|
"predicates-core",
|
||||||
|
"predicates-tree",
|
||||||
|
"wait-timeout",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -152,6 +153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -202,14 +204,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cast"
|
||||||
version = "1.2.53"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
dependencies = [
|
|
||||||
"find-msvc-tools",
|
|
||||||
"shlex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@@ -224,16 +222,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "ciborium"
|
||||||
version = "0.4.43"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"ciborium-io",
|
||||||
"js-sys",
|
"ciborium-ll",
|
||||||
"num-traits",
|
"serde",
|
||||||
"wasm-bindgen",
|
]
|
||||||
"windows-link",
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium-io"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium-ll"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||||
|
dependencies = [
|
||||||
|
"ciborium-io",
|
||||||
|
"half",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -285,6 +297,16 @@ version = "0.7.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_mangen"
|
||||||
|
version = "0.2.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ea63a92086df93893164221ad4f24142086d535b3a0957b9b9bea2dc86301"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"roff",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -313,10 +335,40 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "criterion"
|
||||||
version = "0.8.7"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
||||||
|
dependencies = [
|
||||||
|
"anes",
|
||||||
|
"cast",
|
||||||
|
"ciborium",
|
||||||
|
"clap",
|
||||||
|
"criterion-plot",
|
||||||
|
"is-terminal",
|
||||||
|
"itertools",
|
||||||
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"oorandom",
|
||||||
|
"plotters",
|
||||||
|
"rayon",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"tinytemplate",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "criterion-plot"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||||
|
dependencies = [
|
||||||
|
"cast",
|
||||||
|
"itertools",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
@@ -366,6 +418,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "csv"
|
name = "csv"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -387,6 +445,21 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "difflib"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "document-features"
|
name = "document-features"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@@ -425,10 +498,19 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "fastrand"
|
||||||
version = "0.1.8"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
@@ -447,6 +529,18 @@ dependencies = [
|
|||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.18"
|
version = "0.4.18"
|
||||||
@@ -460,6 +554,17 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -489,45 +594,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humansize"
|
|
||||||
version = "2.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
|
||||||
dependencies = [
|
|
||||||
"libm",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone"
|
|
||||||
version = "0.1.64"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
|
||||||
dependencies = [
|
|
||||||
"android_system_properties",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"iana-time-zone-haiku",
|
|
||||||
"js-sys",
|
|
||||||
"log",
|
|
||||||
"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]]
|
[[package]]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
@@ -555,12 +624,32 @@ dependencies = [
|
|||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.2"
|
version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
@@ -589,12 +678,6 @@ version = "0.2.180"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libm"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -628,6 +711,18 @@ version = "2.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "normalize-line-endings"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -649,6 +744,12 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oorandom"
|
||||||
|
version = "11.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
@@ -672,6 +773,40 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"plotters-backend",
|
||||||
|
"plotters-svg",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters-backend"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters-svg"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||||
|
dependencies = [
|
||||||
|
"plotters-backend",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -681,6 +816,36 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates"
|
||||||
|
version = "3.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"difflib",
|
||||||
|
"float-cmp",
|
||||||
|
"normalize-line-endings",
|
||||||
|
"predicates-core",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-core"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-tree"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
|
||||||
|
dependencies = [
|
||||||
|
"predicates-core",
|
||||||
|
"termtree",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
@@ -728,6 +893,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radium"
|
name = "radium"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -761,7 +932,7 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -813,6 +984,18 @@ dependencies = [
|
|||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@@ -868,6 +1051,12 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roff"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust_decimal"
|
name = "rust_decimal"
|
||||||
version = "1.40.0"
|
version = "1.40.0"
|
||||||
@@ -985,12 +1174,6 @@ dependencies = [
|
|||||||
"zmij",
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "shlex"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simdutf8"
|
name = "simdutf8"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -999,23 +1182,25 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sized"
|
name = "sized"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"assert_cmd",
|
||||||
"byte-unit",
|
"byte-unit",
|
||||||
"chrono",
|
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
|
"clap_mangen",
|
||||||
"colored",
|
"colored",
|
||||||
"comfy-table",
|
"comfy-table",
|
||||||
|
"criterion",
|
||||||
"csv",
|
"csv",
|
||||||
"humansize",
|
|
||||||
"ignore",
|
"ignore",
|
||||||
|
"predicates",
|
||||||
"rayon",
|
"rayon",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
"walkdir",
|
"tempfile",
|
||||||
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1080,6 +1265,66 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termtree"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde_core",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinytemplate"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@@ -1171,6 +1416,15 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -1187,6 +1441,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.2+wasi-0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.108"
|
version = "0.2.108"
|
||||||
@@ -1232,6 +1495,16 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.85"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -1263,65 +1536,12 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-core"
|
|
||||||
version = "0.62.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-implement",
|
|
||||||
"windows-interface",
|
|
||||||
"windows-link",
|
|
||||||
"windows-result",
|
|
||||||
"windows-strings",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-implement"
|
|
||||||
version = "0.60.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-interface"
|
|
||||||
version = "0.59.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.114",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-result"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-strings"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
@@ -1413,6 +1633,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wyz"
|
name = "wyz"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|||||||
+39
-5
@@ -1,13 +1,36 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sized"
|
name = "sized"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
description = "A modern, fast, and concurrent disk usage analyzer"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://gitea.speelman.ca/gamertan/sized"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
categories = ["command-line-utilities", "filesystem"]
|
||||||
|
keywords = ["du", "size", "disk", "usage", "parallel"]
|
||||||
|
|
||||||
|
[package.metadata.deb]
|
||||||
|
maintainer = "Cole Speelman"
|
||||||
|
copyright = "2026, Cole Speelman"
|
||||||
|
license-file = ["LICENSE", "4"]
|
||||||
|
extended-description = """
|
||||||
|
sized is a modern, fast, and concurrent disk usage analyzer.
|
||||||
|
Features include apparent vs disk usage, binary/decimal units, and gitignore support.
|
||||||
|
"""
|
||||||
|
section = "utils"
|
||||||
|
priority = "optional"
|
||||||
|
assets = [
|
||||||
|
["target/release/sized", "usr/bin/sized", "755"],
|
||||||
|
["sized.1", "usr/share/man/man1/sized.1", "644"],
|
||||||
|
["README.md", "usr/share/doc/sized/README.md", "644"],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
walkdir = "2.3"
|
|
||||||
rayon = "1.8"
|
rayon = "1.8"
|
||||||
humansize = "2.1"
|
|
||||||
colored = "2.0"
|
colored = "2.0"
|
||||||
comfy-table = "7.0"
|
comfy-table = "7.0"
|
||||||
byte-unit = "5.0"
|
byte-unit = "5.0"
|
||||||
@@ -16,6 +39,17 @@ ignore = "0.4"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
csv = "1.3"
|
csv = "1.3"
|
||||||
chrono = "0.4"
|
time = { version = "0.3", features = ["macros", "formatting"] }
|
||||||
clap_complete = "4.4"
|
clap_complete = "4.4"
|
||||||
atty = "0.2"
|
clap_mangen = "0.2.31"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_cmd = "2.0"
|
||||||
|
predicates = "3.1"
|
||||||
|
tempfile = "3.10"
|
||||||
|
criterion = "0.5"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "benchmark"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
class Sized < Formula
|
||||||
|
desc "A modern, fast, and concurrent disk usage analyzer"
|
||||||
|
homepage "https://gitea.speelman.ca/gamertan/sized"
|
||||||
|
url "https://gitea.speelman.ca/gamertan/sized/archive/v0.2.0.tar.gz"
|
||||||
|
sha256 "REPLACE_WITH_ACTUAL_SHA256" # Run `shasum -a 256 v0.2.0.tar.gz` after upload
|
||||||
|
license "MIT"
|
||||||
|
|
||||||
|
depends_on "rust" => :build
|
||||||
|
|
||||||
|
def install
|
||||||
|
system "cargo", "install", *std_cargo_args
|
||||||
|
|
||||||
|
# Install man page
|
||||||
|
man1.install "sized.1"
|
||||||
|
|
||||||
|
# Generate and install shell completions
|
||||||
|
generate_completions_from_executable(bin/"sized", "--completions")
|
||||||
|
end
|
||||||
|
|
||||||
|
test do
|
||||||
|
system "#{bin}/sized", "--version"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Cole Speelman
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
# Sized User Manual
|
||||||
|
|
||||||
|
`sized` is a modern, fast, and concurrent disk usage analyzer for the command line, written in Rust. It is designed to provide quick insights into directory sizes with a focus on readability and flexibility.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Basic Usage](#basic-usage)
|
||||||
|
- [Filtering and sorting](#filtering-and-sorting)
|
||||||
|
- [Output Formats](#output-formats)
|
||||||
|
- [Advanced Features](#advanced-features)
|
||||||
|
- [Comparison Mode](#comparison-mode)
|
||||||
|
- [Gitignore Support](#gitignore-support)
|
||||||
|
- [Concurrency](#concurrency)
|
||||||
|
- [Exporting Data](#exporting-data)
|
||||||
|
- [Shell Completions](#shell-completions)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### From Binaries (Recommended)
|
||||||
|
Download the latest pre-compiled binaries from the [Gitea Releases](https://gitea.speelman.ca/gamertan/sized/releases) page.
|
||||||
|
|
||||||
|
### From Source
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.speelman.ca/gamertan/sized.git
|
||||||
|
cd sized
|
||||||
|
make install # Installs binary and man page
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
By default, `sized` analyzes the current directory recursively and displays a table of the immediate children.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sized [path]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Concepts
|
||||||
|
- **Apparent Size**: The logical size of the entry (file length). This is what most file explorers show.
|
||||||
|
- **Disk Usage**: The actual physical space consumed on disk (Blocks * 512 bytes). This is the "true" footprint.
|
||||||
|
- **Blocks**: The actual filesystem blocks allocated. Useful for identifying sparse files or filesystem overhead.
|
||||||
|
|
||||||
|
### Path Display
|
||||||
|
- **Relative Path** (Default): `sized` shows paths relative to the current directory.
|
||||||
|
- **Full Path**: Use `-f` or `--path-full` to see absolute paths (e.g., `/Users/dev/project`).
|
||||||
|
- **Relative Toggle**: Use `--path-relative` to explicitly force relative paths (useful if overriding aliases).
|
||||||
|
|
||||||
|
### Depth Control (`-d` / `--depth`)
|
||||||
|
By default, `sized` shows the immediate children of the target directory (depth 0). You can increase the recursion depth shown in the output.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show current directory and its children's children
|
||||||
|
sized -d 1
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Regardless of the display depth, `sized` always calculates the *total* size of all subdirectories accurately by traversing the entire tree.
|
||||||
|
|
||||||
|
## Filtering and Sorting
|
||||||
|
|
||||||
|
### Sorting (`--sort`)
|
||||||
|
Sort the output table by a specific column. `sized` supports multi-column sorting.
|
||||||
|
|
||||||
|
**Syntax**: `--sort <COLUMN:DIRECTION>,[COLUMN:DIRECTION]`
|
||||||
|
- **Columns**: `name` (n), `size` (s), `type` (t), `blocks` (b)
|
||||||
|
- **Directions**: `asc` (a), `dsc` (d)
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
```bash
|
||||||
|
# Sort by size (largest first) - Default behavior
|
||||||
|
sized --sort size:dsc
|
||||||
|
|
||||||
|
# Primary sort by Type, secondary sort by Size descending
|
||||||
|
sized --sort type:asc,size:dsc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Minimum Size (`-m` / `--min-size`)
|
||||||
|
Hide entries smaller than a specific size to reduce noise. Supports standard units (KB, MB, GB, etc.).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show only items larger than 100MB
|
||||||
|
sized -m 100MB
|
||||||
|
```
|
||||||
|
|
||||||
|
### Limiting Results (`-n` / `--number`)
|
||||||
|
Limit the number of rows displayed in each table.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show only the top 10 largest items
|
||||||
|
sized --sort size:dsc -n 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Formats (`--format`)
|
||||||
|
|
||||||
|
`sized` supports multiple output formats for integration with other tools.
|
||||||
|
|
||||||
|
### Table (Default)
|
||||||
|
The standard human-readable ASCII table with colors.
|
||||||
|
```bash
|
||||||
|
sized --format text
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSV
|
||||||
|
Comma-Separated Values.
|
||||||
|
```bash
|
||||||
|
sized --format csv
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON
|
||||||
|
Computed metrics in NDJSON format.
|
||||||
|
```bash
|
||||||
|
sized --format json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Unit System (`--units`)
|
||||||
|
Switch between Binary (IEC) and Decimal (SI) units for the output.
|
||||||
|
|
||||||
|
- **Binary** (Default): GiB, MiB, KiB (multiples of 1024).
|
||||||
|
- **Decimal**: GB, MB, KB (multiples of 1000).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use decimal units (SI)
|
||||||
|
sized --units decimal
|
||||||
|
```
|
||||||
|
|
||||||
|
### Precision (`--precision`)
|
||||||
|
Specify the number of decimal places for formatted sizes. Defaults to `2`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Higher precision for small files
|
||||||
|
sized --precision 4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comparison Mode (`--compare`)
|
||||||
|
Compare the total physical size of a directory against its "filtered" size (files that would be included in a commit, respecting `.gitignore`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show both total and non-ignored metrics
|
||||||
|
sized -i --compare
|
||||||
|
```
|
||||||
|
This adds **"Filtered"** rows and headers to the output, allowing you to see how much space is consumed by ignored files (like `node_modules` or `target`).
|
||||||
|
|
||||||
|
### Gitignore Support (`-i` / `--ignore`)
|
||||||
|
Respect `.ignore` and `.gitignore` files during traversal. This is highly recommended for developer workflows.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Exclude git-ignored files from calculations
|
||||||
|
sized -i
|
||||||
|
```
|
||||||
|
|
||||||
|
### Concurrency (`-j` / `--threads`)
|
||||||
|
`sized` uses parallel processing powered by `rayon` and `ignore`. By default, it uses a number of threads equal to your logical CPU cores.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Limit to 4 threads on a high-core system
|
||||||
|
sized -j 4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exporting Data (`--save`)
|
||||||
|
Save the output directly to a file. If no filename is provided, `sized` generates a timestamped one (e.g., `sized_report_20260122_1430.txt`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Save to an auto-generated file
|
||||||
|
sized --save
|
||||||
|
|
||||||
|
# Save to a specific path
|
||||||
|
sized --save reports/my_audit.json --format json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shell Completions
|
||||||
|
|
||||||
|
Generate shell completion scripts for Bash, Zsh, or Fish.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate for Zsh
|
||||||
|
sized --completions zsh > _sized
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation (Zsh example)
|
||||||
|
1. Generate the completion file: `sized --completions zsh > ~/.zfunc/_sized`
|
||||||
|
2. Add to your `.zshrc`: `fpath+=~/.zfunc; autoload -Uz compinit && compinit`
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
PREFIX ?= /usr/local
|
||||||
|
BINDIR = $(PREFIX)/bin
|
||||||
|
MANDIR = $(PREFIX)/share/man/man1
|
||||||
|
|
||||||
|
.PHONY: all build install clean help
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
install: build
|
||||||
|
install -d $(BINDIR)
|
||||||
|
install -m 755 target/release/sized $(BINDIR)/sized
|
||||||
|
install -d $(MANDIR)
|
||||||
|
install -m 644 sized.1 $(MANDIR)/sized.1
|
||||||
|
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Usage:"
|
||||||
|
@echo " make build - Build the optimized binary"
|
||||||
|
@echo " make install - Install binary and man page (may require sudo)"
|
||||||
|
@echo " make clean - Remove build artifacts"
|
||||||
@@ -6,22 +6,71 @@ A fast, concurrent, and feature-rich command-line tool for visualizing disk usag
|
|||||||
|
|
||||||
- **Fast & Concurrent**: Uses `rayon` to process directories in parallel, making it extremely fast on modern multi-core systems.
|
- **Fast & Concurrent**: Uses `rayon` to process directories in parallel, making it extremely fast on modern multi-core systems.
|
||||||
- **Rich Output**: Beautifully formatted tables with colors, distinguishing files and directories.
|
- **Rich Output**: Beautifully formatted tables with colors, distinguishing files and directories.
|
||||||
- **Dual Units**: Displays sizes in both Binary (MiB, GiB) and Decimal (MB, GB) units for clarity.
|
- **Physical vs logical**: Clearly separates **Apparent Size** (logical) from **Disk Usage** (actual blocks), essential for cloud files and sparse storage.
|
||||||
|
- **Unit Selection**: Switch between Binary (IEC) and Decimal (SI) unit systems via `--units`.
|
||||||
|
- **Precision control**: Configure decimal places using `--precision`.
|
||||||
- **Advanced Filtering**: Filter by minimum size (`-m 10MB`) to find large items quickly.
|
- **Advanced Filtering**: Filter by minimum size (`-m 10MB`) to find large items quickly.
|
||||||
- **Sorting**: Flexible sorting by name, type, size, or block count (`--sort`).
|
- **Sorting**: Flexible sorting by name, type, size (apparent), or blocks (usage) (`--sort`).
|
||||||
- **Gitignore Support**: Respects `.gitignore` and `.ignore` files to keep output clean (`-i`).
|
- **Gitignore Support**: Respects `.gitignore` and `.ignore` files to keep output clean (`-i`).
|
||||||
- **Export Options**: Export data to CSV or JSON for further analysis (`--format csv/json`).
|
- **Export Options**: Export data to CSV or JSON for further analysis (`--format csv/json`).
|
||||||
- **Save to File**: Automatically save reports with timestamped filenames (`--save`).
|
- **Save to File**: Automatically save reports with timestamped filenames (`--save`).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### From Source
|
### 🚀 Direct Download (macOS & Linux)
|
||||||
Ensure you have Rust and Cargo installed. Clone the repository and install:
|
Download the latest archive for your architecture from the [Releases](https://gitea.speelman.ca/gamertan/sized/releases) page.
|
||||||
|
|
||||||
|
1. **Extract the archive**:
|
||||||
|
```bash
|
||||||
|
tar -xzf sized-v0.2.0-Darwin-arm64.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install Binary & Man Page**:
|
||||||
|
```bash
|
||||||
|
# Move binary to path
|
||||||
|
sudo mv sized /usr/local/bin/
|
||||||
|
|
||||||
|
# Install man page
|
||||||
|
sudo mkdir -p /usr/local/share/man/man1
|
||||||
|
sudo cp sized.1 /usr/local/share/man/man1/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **macOS Security (Gatekeeper)**:
|
||||||
|
Since the binary isn't code-signed for the App Store, macOS may block it. To allow it:
|
||||||
|
```bash
|
||||||
|
sudo xattr -d com.apple.quarantine /usr/local/bin/sized
|
||||||
|
```
|
||||||
|
*Alternatively, run `sized` once, let it fail, then go to **System Settings > Privacy & Security** and click **"Allow Anyway"**.*
|
||||||
|
|
||||||
|
### 🍺 Homebrew (macOS & Linux)
|
||||||
|
If you have a homebrew tap:
|
||||||
|
```bash
|
||||||
|
brew install gamertan/tap/sized
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📦 Debian / Ubuntu (.deb)
|
||||||
|
1. Download the `.deb` package from the [Releases](https://gitea.speelman.ca/gamertan/sized/releases) page.
|
||||||
|
2. Install using `dpkg`:
|
||||||
|
```bash
|
||||||
|
sudo dpkg -i sized_0.2.0_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🦀 From Source (Rust toolchain required)
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.speelman.ca/gamertan/sized.git
|
||||||
|
cd sized
|
||||||
|
make install # Installs binary and man page
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, via Cargo:
|
||||||
```bash
|
```bash
|
||||||
cargo install --path .
|
cargo install --path .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- **[Manual](MANUAL.md)**: Detailed explanations of all flags and features.
|
||||||
|
- **[Man Page](sized.1)**: Standard unix man pages (installed via `make install`).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Basic Usage
|
### Basic Usage
|
||||||
@@ -41,7 +90,9 @@ sized /path/to/directory
|
|||||||
|------|-------------|---------|
|
|------|-------------|---------|
|
||||||
| `-d`, `--depth` | Recursion depth (0 = current dir only) | `sized -d 1` |
|
| `-d`, `--depth` | Recursion depth (0 = current dir only) | `sized -d 1` |
|
||||||
| `-m`, `--min-size` | Filter by minimum size | `sized -m 100MB` |
|
| `-m`, `--min-size` | Filter by minimum size | `sized -m 100MB` |
|
||||||
| `-s`, `--sort` | Sort columns (size, name, type, blocks) | `sized --sort size:asc` |
|
| `--sort` | Sort columns (name, type, size, blocks) | `sized --sort size:asc` |
|
||||||
|
| `--units` | Unit system (binary, decimal) | `sized --units decimal` |
|
||||||
|
| `--precision` | Decimal places for sizes | `sized --precision 3` |
|
||||||
| `-f`, `--path-full` | Force absolute paths in headers | `sized -f` |
|
| `-f`, `--path-full` | Force absolute paths in headers | `sized -f` |
|
||||||
| `-i`, `--ignore` | Respect .gitignore files | `sized -i` |
|
| `-i`, `--ignore` | Respect .gitignore files | `sized -i` |
|
||||||
| `-j`, `--threads` | Set number of threads | `sized -j 4` |
|
| `-j`, `--threads` | Set number of threads | `sized -j 4` |
|
||||||
@@ -55,9 +106,32 @@ sized /path/to/directory
|
|||||||
sized . -d 1 -i -m 50MB --format csv --save
|
sized . -d 1 -i -m 50MB --format csv --save
|
||||||
```
|
```
|
||||||
|
|
||||||
**Generate Shell Completions (Zsh):**
|
### Generate Shell Completions
|
||||||
|
|
||||||
|
**Bash:**
|
||||||
|
Add the following to your `.bashrc` or `.bash_profile`:
|
||||||
```bash
|
```bash
|
||||||
sized --completions zsh > _sized
|
# Option 1: Source directly
|
||||||
|
source <(sized --completions bash)
|
||||||
|
|
||||||
|
# Option 2: Save to file (safer startup time)
|
||||||
|
sized --completions bash > ~/.sized_completions.bash
|
||||||
|
echo "source ~/.sized_completions.bash" >> ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
**Zsh:**
|
||||||
|
```bash
|
||||||
|
# In your .zshrc
|
||||||
|
sized --completions zsh > /usr/local/share/zsh/site-functions/_sized
|
||||||
|
# OR
|
||||||
|
sized --completions zsh > ~/.zfunc/_sized
|
||||||
|
fpath+=~/.zfunc
|
||||||
|
autoload -Uz compinit && compinit
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fish:**
|
||||||
|
```bash
|
||||||
|
sized --completions fish > ~/.config/fish/completions/sized.fish
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
# Project Release Process
|
||||||
|
|
||||||
|
This document defines the process for versioning and distributing the `sized` project independently of the Git hosting provider (GitHub, GitLab, Gitea).
|
||||||
|
|
||||||
|
## 1. Versioning and Tagging
|
||||||
|
|
||||||
|
The project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
1. Synchronize the version in `Cargo.toml`.
|
||||||
|
2. Update `CHANGELOG.md` with the release notes.
|
||||||
|
3. Commit and create a git tag:
|
||||||
|
```bash
|
||||||
|
git tag -a v0.2.0 -m "Release v0.2.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Asset Generation
|
||||||
|
|
||||||
|
The `scripts/release.sh` script is the authoritative way to generate release assets locally or in any CI environment.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/release.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script generates:
|
||||||
|
- **Optimized Binary**: The production-ready `sized` executable.
|
||||||
|
- **Documentation**: The `sized.1` man page.
|
||||||
|
- **Shell Completions**: Scripts for Bash, Zsh, and Fish.
|
||||||
|
- **System Installers**: A `.deb` package for Debian-based Linux distributions.
|
||||||
|
|
||||||
|
All assets are gathered in the `dist/v<VERSION>/` directory.
|
||||||
|
|
||||||
|
## 3. Distribution Channels
|
||||||
|
|
||||||
|
### crates.io
|
||||||
|
To update the project on the central Rust registry:
|
||||||
|
```bash
|
||||||
|
cargo publish
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Package Managers
|
||||||
|
For Homebrew, GitLab, or Gitea-based distribution:
|
||||||
|
1. Push the git tag to your SCM.
|
||||||
|
2. Run the release script to generate assets.
|
||||||
|
3. Attach the contents of the `dist/` folder to your SCM's "Release" or "Tag" entry.
|
||||||
|
4. Update downstream formulas (like `homebrew-tap`) with the new source URL and SHA256 of the generated tarball.
|
||||||
|
|
||||||
|
## 4. Automation & Hooks
|
||||||
|
|
||||||
|
To automate asset generation locally, you can use a git `post-checkout` or a wrapper script. Since gitea and gitlab use different CI formats, it is recommended to simply call `./scripts/release.sh` within your preferred CI runner (e.g., `gitlab-ci.yml` or `gitea-actions`).
|
||||||
|
|
||||||
|
## 5. Manual Installation
|
||||||
|
For system-wide installation from source, use the `Makefile`:
|
||||||
|
```bash
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
Defaults to `PREFIX=/usr/local`. Overridable via `PREFIX=/your/path make install`.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
use sized::build_tree;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use tempfile::TempDir; // Ensure this is pub in lib.rs
|
||||||
|
|
||||||
|
fn setup_test_dir() -> TempDir {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let base_path = temp_dir.path();
|
||||||
|
|
||||||
|
// Create a moderately deep structure
|
||||||
|
for i in 0..10 {
|
||||||
|
let dir_path = base_path.join(format!("dir_{}", i));
|
||||||
|
fs::create_dir(&dir_path).unwrap();
|
||||||
|
for j in 0..10 {
|
||||||
|
let file_path = dir_path.join(format!("file_{}.txt", j));
|
||||||
|
let mut file = File::create(file_path).unwrap();
|
||||||
|
writeln!(file, "Some content for file {}-{}", i, j).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
temp_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
let temp_dir = setup_test_dir();
|
||||||
|
let path = temp_dir.path();
|
||||||
|
|
||||||
|
c.bench_function("build_tree small", |b| {
|
||||||
|
b.iter(|| build_tree(black_box(path), black_box(false), black_box(false)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, criterion_benchmark);
|
||||||
|
criterion_main!(benches);
|
||||||
Executable
+53
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
VERSION=$(grep '^version =' Cargo.toml | cut -d '"' -f 2)
|
||||||
|
DIST_DIR="dist/v$VERSION"
|
||||||
|
|
||||||
|
echo "Building release assets for sized v$VERSION..."
|
||||||
|
|
||||||
|
# 1. Clean and Prepare
|
||||||
|
rm -rf "$DIST_DIR"
|
||||||
|
mkdir -p "$DIST_DIR"
|
||||||
|
|
||||||
|
# 2. Build Release Binary
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# 3. Generate Man Page
|
||||||
|
cargo run --release -- --generate-man-page "$DIST_DIR"
|
||||||
|
|
||||||
|
# 4. Generate Completions
|
||||||
|
cargo run --release -- --completions bash > "$DIST_DIR/sized.bash"
|
||||||
|
cargo run --release -- --completions zsh > "$DIST_DIR/_sized"
|
||||||
|
cargo run --release -- --completions fish > "$DIST_DIR/sized.fish"
|
||||||
|
|
||||||
|
# 5. Build Debian Package (if cargo-deb is installed)
|
||||||
|
if command -v cargo-deb &> /dev/null; then
|
||||||
|
echo "Building Debian package..."
|
||||||
|
# On macOS, we use --no-strip to avoid 'unrecognized option: --strip-unneeded'
|
||||||
|
# and --no-build because we already built the release binary.
|
||||||
|
cargo deb --no-build --no-strip
|
||||||
|
cp target/debian/*.deb "$DIST_DIR/"
|
||||||
|
echo "Note: .deb package contains the binary for $(uname -s)-$(uname -m)"
|
||||||
|
else
|
||||||
|
echo "Warning: cargo-deb not found. Skipping .deb packaging."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Build Alpine Package (Placeholder/Hook)
|
||||||
|
# Note: For real .apk building, one usually uses a docker container or abuild.
|
||||||
|
# This serves as a reminder for Alpine users.
|
||||||
|
if [ -f "APKBUILD" ] && command -v abuild &> /dev/null; then
|
||||||
|
echo "Building Alpine package..."
|
||||||
|
abuild -r
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7. Create General Release Tarball
|
||||||
|
echo "Creating release tarball..."
|
||||||
|
tar -czf "dist/sized-v$VERSION-$(uname -s)-$(uname -m).tar.gz" -C "$DIST_DIR" .
|
||||||
|
|
||||||
|
# 8. Copy Binary
|
||||||
|
cp target/release/sized "$DIST_DIR/"
|
||||||
|
|
||||||
|
echo "Success! Assets are ready in $DIST_DIR"
|
||||||
|
ls -F "$DIST_DIR"
|
||||||
|
echo "Tarball: dist/sized-v$VERSION-$(uname -s)-$(uname -m).tar.gz"
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
.ie \n(.g .ds Aq \(aq
|
||||||
|
.el .ds Aq '
|
||||||
|
.TH sized 1 "sized 0.2.0"
|
||||||
|
.SH NAME
|
||||||
|
sized
|
||||||
|
.SH SYNOPSIS
|
||||||
|
\fBsized\fR [\fB\-v\fR|\fB\-\-version\fR] [\fB\-m\fR|\fB\-\-min\-size\fR] [\fB\-n\fR|\fB\-\-number\fR] [\fB\-\-sort\fR] [\fB\-d\fR|\fB\-\-depth\fR] [\fB\-f\fR|\fB\-\-path\-full\fR] [\fB\-\-path\-relative\fR] [\fB\-j\fR|\fB\-\-threads\fR] [\fB\-\-completions\fR] [\fB\-i\fR|\fB\-\-ignore\fR] [\fB\-\-format\fR] [\fB\-\-save\fR] [\fB\-\-precision\fR] [\fB\-\-units\fR] [\fB\-\-compare\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fIPATH\fR]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
\fB\-v\fR, \fB\-\-version\fR
|
||||||
|
Print version
|
||||||
|
.TP
|
||||||
|
\fB\-m\fR, \fB\-\-min\-size\fR \fI<MIN_SIZE>\fR
|
||||||
|
Minimum size filter (e.g. "10MB", "1gb")
|
||||||
|
.TP
|
||||||
|
\fB\-n\fR, \fB\-\-number\fR \fI<NUMBER>\fR
|
||||||
|
Limit the number of results per table
|
||||||
|
.TP
|
||||||
|
\fB\-\-sort\fR \fI<SORT>\fR
|
||||||
|
Sort by columns (e.g. "type:asc,size:dsc"). defaults to size:dsc. Columns: name, type, size, blocks
|
||||||
|
.TP
|
||||||
|
\fB\-d\fR, \fB\-\-depth\fR \fI<DEPTH>\fR [default: 0]
|
||||||
|
Depth to traverse (0 = only immediate children)
|
||||||
|
.TP
|
||||||
|
\fB\-f\fR, \fB\-\-path\-full\fR
|
||||||
|
Display full absolute paths
|
||||||
|
.TP
|
||||||
|
\fB\-\-path\-relative\fR
|
||||||
|
Display paths relative to current directory (default)
|
||||||
|
.TP
|
||||||
|
\fB\-j\fR, \fB\-\-threads\fR \fI<THREADS>\fR
|
||||||
|
Number of threads to use (defaults to available logical CPUs)
|
||||||
|
.TP
|
||||||
|
\fB\-\-completions\fR \fI<COMPLETIONS>\fR
|
||||||
|
Generate shell completions
|
||||||
|
.br
|
||||||
|
|
||||||
|
.br
|
||||||
|
[\fIpossible values: \fRbash, elvish, fish, powershell, zsh]
|
||||||
|
.TP
|
||||||
|
\fB\-i\fR, \fB\-\-ignore\fR
|
||||||
|
Respect .gitignore and .ignore files
|
||||||
|
.TP
|
||||||
|
\fB\-\-format\fR \fI<FORMAT>\fR [default: text]
|
||||||
|
Output format
|
||||||
|
.br
|
||||||
|
|
||||||
|
.br
|
||||||
|
[\fIpossible values: \fRtext, csv, json]
|
||||||
|
.TP
|
||||||
|
\fB\-\-save\fR [\fI<SAVE>\fR]
|
||||||
|
Save output to file (optional filename, defaults to timestamped name)
|
||||||
|
.TP
|
||||||
|
\fB\-\-precision\fR \fI<PRECISION>\fR [default: 2]
|
||||||
|
Precision for size output (decimal places)
|
||||||
|
.TP
|
||||||
|
\fB\-\-units\fR \fI<UNITS>\fR [default: binary]
|
||||||
|
Unit system to use
|
||||||
|
.br
|
||||||
|
|
||||||
|
.br
|
||||||
|
[\fIpossible values: \fRbinary, decimal]
|
||||||
|
.TP
|
||||||
|
\fB\-\-compare\fR
|
||||||
|
Show comparison between total and non\-ignored files
|
||||||
|
.TP
|
||||||
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
|
Print help
|
||||||
|
.TP
|
||||||
|
[\fIPATH\fR] [default: .]
|
||||||
|
Directory to analyze
|
||||||
|
.SH VERSION
|
||||||
|
v0.2.0
|
||||||
+813
@@ -0,0 +1,813 @@
|
|||||||
|
use byte_unit::{Byte, UnitType};
|
||||||
|
|
||||||
|
use clap::{CommandFactory, Parser, ValueEnum};
|
||||||
|
use clap_complete::{generate, Shell};
|
||||||
|
use colored::*;
|
||||||
|
use comfy_table::presets::UTF8_FULL;
|
||||||
|
use comfy_table::{Attribute, Cell, CellAlignment, Color, ContentArrangement, Table};
|
||||||
|
use csv::WriterBuilder;
|
||||||
|
use ignore::WalkBuilder;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug, Clone)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
#[command(disable_version_flag = true)]
|
||||||
|
pub struct Args {
|
||||||
|
/// Print version
|
||||||
|
#[arg(short = 'v', long, action = clap::ArgAction::Version)]
|
||||||
|
pub version: Option<bool>,
|
||||||
|
|
||||||
|
/// Directory to analyze
|
||||||
|
#[arg(default_value = ".")]
|
||||||
|
pub path: PathBuf,
|
||||||
|
|
||||||
|
/// Minimum size filter (e.g. "10MB", "1gb")
|
||||||
|
#[arg(short = 'm', long)]
|
||||||
|
pub min_size: Option<String>,
|
||||||
|
|
||||||
|
/// Limit the number of results per table
|
||||||
|
#[arg(short = 'n', long)]
|
||||||
|
pub number: Option<usize>,
|
||||||
|
|
||||||
|
/// Sort by columns (e.g. "type:asc,size:dsc").
|
||||||
|
/// defaults to size:dsc.
|
||||||
|
/// Columns: name, type, size, blocks.
|
||||||
|
#[arg(long)]
|
||||||
|
pub sort: Option<String>,
|
||||||
|
|
||||||
|
/// Depth to traverse (0 = only immediate children)
|
||||||
|
#[arg(short, long, default_value = "0")]
|
||||||
|
pub depth: usize,
|
||||||
|
|
||||||
|
/// Display full absolute paths
|
||||||
|
#[arg(short = 'f', long, conflicts_with = "path_relative")]
|
||||||
|
pub path_full: bool,
|
||||||
|
|
||||||
|
/// Display paths relative to current directory (default)
|
||||||
|
#[arg(long)]
|
||||||
|
pub path_relative: bool,
|
||||||
|
|
||||||
|
/// Number of threads to use (defaults to available logical CPUs)
|
||||||
|
#[arg(short = 'j', long = "threads")]
|
||||||
|
pub threads: Option<usize>,
|
||||||
|
|
||||||
|
/// Generate shell completions
|
||||||
|
#[arg(long, value_enum)]
|
||||||
|
pub completions: Option<Shell>,
|
||||||
|
|
||||||
|
/// Respect .gitignore and .ignore files
|
||||||
|
#[arg(short = 'i', long)]
|
||||||
|
pub ignore: bool,
|
||||||
|
|
||||||
|
/// Output format
|
||||||
|
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
|
||||||
|
pub format: OutputFormat,
|
||||||
|
|
||||||
|
/// Save output to file (optional filename, defaults to timestamped name)
|
||||||
|
#[arg(long, num_args(0..=1), default_missing_value = "_AUTO_")]
|
||||||
|
pub save: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Precision for size output (decimal places)
|
||||||
|
#[arg(long, default_value = "2")]
|
||||||
|
pub precision: usize,
|
||||||
|
|
||||||
|
/// Unit system to use
|
||||||
|
#[arg(long, value_enum, default_value_t = UnitSystem::Binary)]
|
||||||
|
pub units: UnitSystem,
|
||||||
|
|
||||||
|
/// Show comparison between total and non-ignored files
|
||||||
|
#[arg(long)]
|
||||||
|
pub compare: bool,
|
||||||
|
|
||||||
|
/// Generate man page to the specified output directory
|
||||||
|
#[arg(long, hide = true)]
|
||||||
|
pub generate_man_page: Option<std::path::PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum OutputFormat {
|
||||||
|
Text,
|
||||||
|
Csv,
|
||||||
|
Json,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum UnitSystem {
|
||||||
|
Binary,
|
||||||
|
Decimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SortColumn {
|
||||||
|
Name,
|
||||||
|
Type,
|
||||||
|
Size,
|
||||||
|
Blocks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SortColumn {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"name" | "n" => Ok(SortColumn::Name),
|
||||||
|
"type" | "t" => Ok(SortColumn::Type),
|
||||||
|
"size" | "s" => Ok(SortColumn::Size),
|
||||||
|
"blocks" | "b" => Ok(SortColumn::Blocks),
|
||||||
|
_ => Err(format!("Unknown column: {}", s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SortDirection {
|
||||||
|
Asc,
|
||||||
|
Dsc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SortDirection {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"asc" | "a" => Ok(SortDirection::Asc),
|
||||||
|
"dsc" | "d" | "desc" => Ok(SortDirection::Dsc),
|
||||||
|
_ => Err(format!("Unknown direction: {}", s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Debug)]
|
||||||
|
pub struct Node {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub size_bytes: u64,
|
||||||
|
pub blocks: u64,
|
||||||
|
pub size_bytes_filtered: u64,
|
||||||
|
pub blocks_filtered: u64,
|
||||||
|
pub entry_type: EntryType,
|
||||||
|
pub accessible: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub children: Vec<Node>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum EntryType {
|
||||||
|
File,
|
||||||
|
Dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EntryType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
EntryType::File => write!(f, "File"),
|
||||||
|
EntryType::Dir => write!(f, "Dir"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct JsonMetrics {
|
||||||
|
pub size_bytes: u64,
|
||||||
|
pub blocks: u64,
|
||||||
|
pub size_bytes_filtered: u64,
|
||||||
|
pub blocks_filtered: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct JsonEntry<'a> {
|
||||||
|
pub path: &'a PathBuf,
|
||||||
|
pub metrics: JsonMetrics,
|
||||||
|
pub entry_type: String,
|
||||||
|
pub accessible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct JsonReport<'a> {
|
||||||
|
pub path: &'a PathBuf,
|
||||||
|
pub total_size: u64,
|
||||||
|
pub blocks: u64,
|
||||||
|
pub total_size_filtered: u64,
|
||||||
|
pub blocks_filtered: u64,
|
||||||
|
pub entries: Vec<JsonEntry<'a>>,
|
||||||
|
pub accessible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(args: Args, mut writer: &mut dyn Write) {
|
||||||
|
if let Some(shell) = args.completions {
|
||||||
|
let mut cmd = Args::command();
|
||||||
|
let name = cmd.get_name().to_string();
|
||||||
|
generate(shell, &mut cmd, name, &mut std::io::stdout());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(out_dir) = args.generate_man_page {
|
||||||
|
let cmd = Args::command();
|
||||||
|
let man = clap_mangen::Man::new(cmd);
|
||||||
|
let mut buffer: Vec<u8> = Default::default();
|
||||||
|
man.render(&mut buffer).expect("Failed to render man page");
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&out_dir).expect("Failed to create man page directory");
|
||||||
|
let file_path = out_dir.join("sized.1");
|
||||||
|
std::fs::write(&file_path, buffer).expect("Failed to write man page");
|
||||||
|
println!("Man page generated at {}", file_path.display());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(threads) = args.threads {
|
||||||
|
rayon::ThreadPoolBuilder::new()
|
||||||
|
.num_threads(threads)
|
||||||
|
.build_global()
|
||||||
|
.ok(); // Ignore error if initialized called multiple times (e.g. in tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_path = args.path.clone();
|
||||||
|
|
||||||
|
if !target_path.exists() {
|
||||||
|
eprintln!("Error: Path '{}' does not exist.", target_path.display());
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_bytes = if let Some(size_str) = &args.min_size {
|
||||||
|
match Byte::parse_str(size_str, true) {
|
||||||
|
Ok(byte) => byte.as_u64(),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error parsing size '{}': {}", size_str, e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let sort_criteria = parse_sort_arg(args.sort.as_deref().unwrap_or("size:dsc"));
|
||||||
|
|
||||||
|
if args.format == OutputFormat::Csv {
|
||||||
|
writeln!(writer, "Path,Type,Size(Bytes),Blocks").expect("Failed to write CSV header");
|
||||||
|
}
|
||||||
|
|
||||||
|
let root_node = build_tree(&target_path, args.ignore, args.compare);
|
||||||
|
|
||||||
|
if root_node.size_bytes < min_bytes {
|
||||||
|
if args.format == OutputFormat::Text {
|
||||||
|
writeln!(writer, "Root directory is smaller than minimum size.").ok();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_node_recursive(&mut writer, &root_node, 0, &args, min_bytes, &sort_criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_tree(path: &Path, ignore: bool, compare: bool) -> Node {
|
||||||
|
let metadata = path.symlink_metadata();
|
||||||
|
|
||||||
|
if let Ok(meta) = metadata {
|
||||||
|
// Treat symlinks as files (nodes) but do not recurse
|
||||||
|
if meta.is_file() || meta.is_symlink() {
|
||||||
|
return Node {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
size_bytes: meta.len(),
|
||||||
|
blocks: meta.blocks(),
|
||||||
|
size_bytes_filtered: meta.len(), // Single file is its own filtered size for now
|
||||||
|
blocks_filtered: meta.blocks(),
|
||||||
|
entry_type: EntryType::File,
|
||||||
|
accessible: true,
|
||||||
|
children: vec![],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can read the directory (handle permissions)
|
||||||
|
if let Err(e) = std::fs::read_dir(path) {
|
||||||
|
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||||
|
// Return an "empty" directory node marked as inaccessible
|
||||||
|
let meta = path.symlink_metadata().ok();
|
||||||
|
let size = meta.as_ref().map(|m| m.len()).unwrap_or(0);
|
||||||
|
let blocks = meta.as_ref().map(|m| m.blocks()).unwrap_or(0);
|
||||||
|
return Node {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
size_bytes: size,
|
||||||
|
blocks,
|
||||||
|
size_bytes_filtered: size,
|
||||||
|
blocks_filtered: blocks,
|
||||||
|
entry_type: EntryType::Dir,
|
||||||
|
accessible: false,
|
||||||
|
children: vec![],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total walker (always everything if compare is true, else respects 'ignore' arg)
|
||||||
|
let total_ignore = if compare { false } else { ignore };
|
||||||
|
let walker_total = WalkBuilder::new(path)
|
||||||
|
.standard_filters(false)
|
||||||
|
.hidden(false)
|
||||||
|
.git_ignore(total_ignore)
|
||||||
|
.ignore(total_ignore)
|
||||||
|
.max_depth(Some(1))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let child_paths_total: Vec<PathBuf> = walker_total
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.path() != path)
|
||||||
|
.map(|e| e.path().to_path_buf())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Filtered set (only if compare is true)
|
||||||
|
let non_ignored_set: std::collections::HashSet<PathBuf> = if compare {
|
||||||
|
let walker_filtered = WalkBuilder::new(path)
|
||||||
|
.standard_filters(false)
|
||||||
|
.hidden(false)
|
||||||
|
.git_ignore(true)
|
||||||
|
.ignore(true)
|
||||||
|
.max_depth(Some(1))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
walker_filtered
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.path() != path)
|
||||||
|
.map(|e| e.path().to_path_buf())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
std::collections::HashSet::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let children: Vec<Node> = child_paths_total
|
||||||
|
.par_iter()
|
||||||
|
.map(|p| build_tree(p, ignore, compare))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut size_bytes = 0;
|
||||||
|
let mut blocks = 0;
|
||||||
|
let mut size_bytes_filtered = 0;
|
||||||
|
let mut blocks_filtered = 0;
|
||||||
|
|
||||||
|
for child in &children {
|
||||||
|
size_bytes += child.size_bytes;
|
||||||
|
blocks += child.blocks;
|
||||||
|
|
||||||
|
if compare {
|
||||||
|
if non_ignored_set.contains(&child.path) {
|
||||||
|
size_bytes_filtered += child.size_bytes_filtered;
|
||||||
|
blocks_filtered += child.blocks_filtered;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
size_bytes_filtered += child.size_bytes_filtered;
|
||||||
|
blocks_filtered += child.blocks_filtered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (self_size, self_blocks) = path
|
||||||
|
.symlink_metadata()
|
||||||
|
.map(|m| (m.len(), m.blocks()))
|
||||||
|
.unwrap_or((0, 0));
|
||||||
|
|
||||||
|
Node {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
size_bytes: size_bytes + self_size,
|
||||||
|
blocks: blocks + self_blocks,
|
||||||
|
size_bytes_filtered: size_bytes_filtered + self_size, // self is always part of self
|
||||||
|
blocks_filtered: blocks_filtered + self_blocks,
|
||||||
|
entry_type: EntryType::Dir,
|
||||||
|
accessible: true,
|
||||||
|
children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_node_recursive(
|
||||||
|
writer: &mut dyn Write,
|
||||||
|
node: &Node,
|
||||||
|
current_depth: usize,
|
||||||
|
args: &Args,
|
||||||
|
min_bytes: u64,
|
||||||
|
sort_criteria: &[(SortColumn, SortDirection)],
|
||||||
|
) {
|
||||||
|
if node.children.is_empty() && node.entry_type == EntryType::Dir {}
|
||||||
|
|
||||||
|
let mut display_children: Vec<&Node> = node
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.size_bytes >= min_bytes)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
display_children.sort_by(|a, b| compare_nodes(a, b, sort_criteria));
|
||||||
|
|
||||||
|
if let Some(n) = args.number {
|
||||||
|
if n < display_children.len() {
|
||||||
|
display_children.truncate(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let show_relative = !args.path_full;
|
||||||
|
let relative_path = if show_relative {
|
||||||
|
let cwd = std::env::current_dir().unwrap_or_default();
|
||||||
|
node.path
|
||||||
|
.strip_prefix(&cwd)
|
||||||
|
.unwrap_or(&node.path)
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
node.path
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap_or(node.path.clone())
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
print_output(
|
||||||
|
writer,
|
||||||
|
&node,
|
||||||
|
&display_children,
|
||||||
|
args,
|
||||||
|
&relative_path,
|
||||||
|
current_depth,
|
||||||
|
);
|
||||||
|
|
||||||
|
if current_depth < args.depth {
|
||||||
|
for child in display_children {
|
||||||
|
if child.entry_type == EntryType::Dir {
|
||||||
|
process_node_recursive(
|
||||||
|
writer,
|
||||||
|
child,
|
||||||
|
current_depth + 1,
|
||||||
|
args,
|
||||||
|
min_bytes,
|
||||||
|
sort_criteria,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compare_nodes(a: &Node, b: &Node, criteria: &[(SortColumn, SortDirection)]) -> Ordering {
|
||||||
|
for (col, dir) in criteria {
|
||||||
|
let order = match col {
|
||||||
|
SortColumn::Name => a.path.file_name().cmp(&b.path.file_name()),
|
||||||
|
SortColumn::Type => a.entry_type.cmp(&b.entry_type),
|
||||||
|
SortColumn::Size => a.size_bytes.cmp(&b.size_bytes),
|
||||||
|
SortColumn::Blocks => a.blocks.cmp(&b.blocks),
|
||||||
|
};
|
||||||
|
|
||||||
|
if order != Ordering::Equal {
|
||||||
|
return match dir {
|
||||||
|
SortDirection::Asc => order,
|
||||||
|
SortDirection::Dsc => order.reverse(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_sort_arg(s: &str) -> Vec<(SortColumn, SortDirection)> {
|
||||||
|
s.split(',')
|
||||||
|
.filter_map(|part| {
|
||||||
|
let parts: Vec<&str> = part.split(':').collect();
|
||||||
|
if parts.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let col_str = parts[0];
|
||||||
|
let col = SortColumn::from_str(col_str).ok()?;
|
||||||
|
|
||||||
|
let dir = if parts.len() > 1 {
|
||||||
|
SortDirection::from_str(parts[1]).unwrap_or(SortDirection::Dsc)
|
||||||
|
} else {
|
||||||
|
match col {
|
||||||
|
SortColumn::Name | SortColumn::Type => SortDirection::Asc,
|
||||||
|
_ => SortDirection::Dsc,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some((col, dir))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_output(
|
||||||
|
writer: &mut dyn Write,
|
||||||
|
parent_node: &Node,
|
||||||
|
children: &[&Node],
|
||||||
|
args: &Args,
|
||||||
|
relative_path: &str,
|
||||||
|
depth: usize,
|
||||||
|
) {
|
||||||
|
match args.format {
|
||||||
|
OutputFormat::Text => print_text(writer, parent_node, children, args, relative_path, depth),
|
||||||
|
OutputFormat::Csv => print_csv(writer, children),
|
||||||
|
OutputFormat::Json => print_json(writer, parent_node, children),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_text(
|
||||||
|
writer: &mut dyn Write,
|
||||||
|
parent_node: &Node,
|
||||||
|
children: &[&Node],
|
||||||
|
args: &Args,
|
||||||
|
relative_path: &str,
|
||||||
|
depth: usize,
|
||||||
|
) {
|
||||||
|
if depth > 0 {
|
||||||
|
writeln!(writer, "{}", "-".repeat(60).dimmed()).ok();
|
||||||
|
}
|
||||||
|
writeln!(writer, "\n{}", relative_path.bold().blue()).ok();
|
||||||
|
|
||||||
|
let unit_type = match args.units {
|
||||||
|
UnitSystem::Binary => UnitType::Binary,
|
||||||
|
UnitSystem::Decimal => UnitType::Decimal,
|
||||||
|
};
|
||||||
|
|
||||||
|
if parent_node.accessible {
|
||||||
|
let total_apparent = Byte::from_u64(parent_node.size_bytes).get_appropriate_unit(unit_type);
|
||||||
|
let total_usage = Byte::from_u64(parent_node.blocks * 512).get_appropriate_unit(unit_type);
|
||||||
|
|
||||||
|
let mut summary = format!(
|
||||||
|
"Total Apparent: {} / Disk Usage: {} ({} blocks)",
|
||||||
|
format!(
|
||||||
|
"{:.precision$} {}",
|
||||||
|
total_apparent.get_value(),
|
||||||
|
total_apparent.get_unit(),
|
||||||
|
precision = args.precision
|
||||||
|
)
|
||||||
|
.bold()
|
||||||
|
.green(),
|
||||||
|
format!(
|
||||||
|
"{:.precision$} {}",
|
||||||
|
total_usage.get_value(),
|
||||||
|
total_usage.get_unit(),
|
||||||
|
precision = args.precision
|
||||||
|
)
|
||||||
|
.bold()
|
||||||
|
.green(),
|
||||||
|
parent_node.blocks
|
||||||
|
);
|
||||||
|
|
||||||
|
if args.compare {
|
||||||
|
let total_apparent_filtered =
|
||||||
|
Byte::from_u64(parent_node.size_bytes_filtered).get_appropriate_unit(unit_type);
|
||||||
|
let total_usage_filtered =
|
||||||
|
Byte::from_u64(parent_node.blocks_filtered * 512).get_appropriate_unit(unit_type);
|
||||||
|
|
||||||
|
summary.push_str(&format!(
|
||||||
|
"\nFiltered Apparent: {} / Filtered Disk Usage: {} ({} blocks)",
|
||||||
|
format!(
|
||||||
|
"{:.precision$} {}",
|
||||||
|
total_apparent_filtered.get_value(),
|
||||||
|
total_apparent_filtered.get_unit(),
|
||||||
|
precision = args.precision
|
||||||
|
)
|
||||||
|
.bold()
|
||||||
|
.cyan(),
|
||||||
|
format!(
|
||||||
|
"{:.precision$} {}",
|
||||||
|
total_usage_filtered.get_value(),
|
||||||
|
total_usage_filtered.get_unit(),
|
||||||
|
precision = args.precision
|
||||||
|
)
|
||||||
|
.bold()
|
||||||
|
.cyan(),
|
||||||
|
parent_node.blocks_filtered
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(writer, "{}", summary).ok();
|
||||||
|
} else {
|
||||||
|
writeln!(writer, "Total size: {}", "Access Denied".bold().red()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if children.is_empty() {
|
||||||
|
if parent_node.accessible {
|
||||||
|
writeln!(writer, "(No children)").ok();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table = Table::new();
|
||||||
|
table
|
||||||
|
.load_preset(UTF8_FULL)
|
||||||
|
.set_content_arrangement(ContentArrangement::Dynamic);
|
||||||
|
|
||||||
|
let mut headers = vec![
|
||||||
|
Cell::new("Name").add_attribute(Attribute::Bold),
|
||||||
|
Cell::new("Type").add_attribute(Attribute::Bold),
|
||||||
|
Cell::new("Apparent")
|
||||||
|
.add_attribute(Attribute::Bold)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
Cell::new("Disk Usage")
|
||||||
|
.add_attribute(Attribute::Bold)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
];
|
||||||
|
|
||||||
|
if args.compare {
|
||||||
|
headers.push(
|
||||||
|
Cell::new("App. Filtered")
|
||||||
|
.add_attribute(Attribute::Bold)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
headers.push(
|
||||||
|
Cell::new("Disk Filtered")
|
||||||
|
.add_attribute(Attribute::Bold)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
headers.push(
|
||||||
|
Cell::new("Blocks")
|
||||||
|
.add_attribute(Attribute::Bold)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
|
||||||
|
table.set_header(headers);
|
||||||
|
|
||||||
|
for child in children {
|
||||||
|
let name = child.path.file_name().unwrap_or_default().to_string_lossy();
|
||||||
|
let color = if child.entry_type == EntryType::Dir {
|
||||||
|
Color::Blue
|
||||||
|
} else {
|
||||||
|
Color::Cyan
|
||||||
|
};
|
||||||
|
|
||||||
|
if child.accessible {
|
||||||
|
let child_byte = Byte::from_u64(child.size_bytes).get_appropriate_unit(unit_type);
|
||||||
|
let disk_usage_byte =
|
||||||
|
Byte::from_u64(child.blocks * 512).get_appropriate_unit(unit_type);
|
||||||
|
|
||||||
|
let mut row = vec![
|
||||||
|
Cell::new(name).fg(color),
|
||||||
|
Cell::new(&child.entry_type).fg(Color::Yellow),
|
||||||
|
Cell::new(format!(
|
||||||
|
"{:.precision$} {}",
|
||||||
|
child_byte.get_value(),
|
||||||
|
child_byte.get_unit(),
|
||||||
|
precision = args.precision
|
||||||
|
))
|
||||||
|
.fg(Color::Green)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
Cell::new(format!(
|
||||||
|
"{:.precision$} {}",
|
||||||
|
disk_usage_byte.get_value(),
|
||||||
|
disk_usage_byte.get_unit(),
|
||||||
|
precision = args.precision
|
||||||
|
))
|
||||||
|
.fg(Color::Green)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
];
|
||||||
|
|
||||||
|
if args.compare {
|
||||||
|
let child_byte_filtered =
|
||||||
|
Byte::from_u64(child.size_bytes_filtered).get_appropriate_unit(unit_type);
|
||||||
|
let disk_usage_byte_filtered =
|
||||||
|
Byte::from_u64(child.blocks_filtered * 512).get_appropriate_unit(unit_type);
|
||||||
|
|
||||||
|
row.push(
|
||||||
|
Cell::new(format!(
|
||||||
|
"{:.precision$} {}",
|
||||||
|
child_byte_filtered.get_value(),
|
||||||
|
child_byte_filtered.get_unit(),
|
||||||
|
precision = args.precision
|
||||||
|
))
|
||||||
|
.fg(Color::Cyan)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
row.push(
|
||||||
|
Cell::new(format!(
|
||||||
|
"{:.precision$} {}",
|
||||||
|
disk_usage_byte_filtered.get_value(),
|
||||||
|
disk_usage_byte_filtered.get_unit(),
|
||||||
|
precision = args.precision
|
||||||
|
))
|
||||||
|
.fg(Color::Cyan)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
row.push(
|
||||||
|
Cell::new(child.blocks)
|
||||||
|
.fg(Color::Green)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
|
||||||
|
table.add_row(row);
|
||||||
|
} else {
|
||||||
|
let mut row = vec![
|
||||||
|
Cell::new(name).fg(color),
|
||||||
|
Cell::new(&child.entry_type).fg(Color::Yellow),
|
||||||
|
Cell::new("N/A")
|
||||||
|
.fg(Color::Red)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
Cell::new("Access Denied")
|
||||||
|
.fg(Color::Red)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
];
|
||||||
|
|
||||||
|
if args.compare {
|
||||||
|
row.push(
|
||||||
|
Cell::new("-")
|
||||||
|
.fg(Color::Red)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
row.push(
|
||||||
|
Cell::new("-")
|
||||||
|
.fg(Color::Red)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
row.push(
|
||||||
|
Cell::new("-")
|
||||||
|
.fg(Color::Red)
|
||||||
|
.set_alignment(CellAlignment::Right),
|
||||||
|
);
|
||||||
|
|
||||||
|
table.add_row(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(writer, "{}", table).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_csv(writer: &mut dyn Write, children: &[&Node]) {
|
||||||
|
let mut wtr = WriterBuilder::new().has_headers(false).from_writer(writer);
|
||||||
|
|
||||||
|
for child in children {
|
||||||
|
wtr.write_record(&[
|
||||||
|
child.path.to_string_lossy().as_ref(),
|
||||||
|
&child.entry_type.to_string(),
|
||||||
|
&child.size_bytes.to_string(),
|
||||||
|
&child.blocks.to_string(),
|
||||||
|
])
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
wtr.flush().ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_json(writer: &mut dyn Write, parent_node: &Node, children: &[&Node]) {
|
||||||
|
let entries_view: Vec<JsonEntry> = children
|
||||||
|
.iter()
|
||||||
|
.map(|c| JsonEntry {
|
||||||
|
path: &c.path,
|
||||||
|
metrics: JsonMetrics {
|
||||||
|
size_bytes: c.size_bytes,
|
||||||
|
blocks: c.blocks,
|
||||||
|
size_bytes_filtered: c.size_bytes_filtered,
|
||||||
|
blocks_filtered: c.blocks_filtered,
|
||||||
|
},
|
||||||
|
entry_type: c.entry_type.to_string(),
|
||||||
|
accessible: c.accessible,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let report = JsonReport {
|
||||||
|
path: &parent_node.path,
|
||||||
|
total_size: parent_node.size_bytes,
|
||||||
|
blocks: parent_node.blocks,
|
||||||
|
total_size_filtered: parent_node.size_bytes_filtered,
|
||||||
|
blocks_filtered: parent_node.blocks_filtered,
|
||||||
|
entries: entries_view,
|
||||||
|
accessible: parent_node.accessible,
|
||||||
|
};
|
||||||
|
|
||||||
|
serde_json::to_writer(&mut *writer, &report).ok();
|
||||||
|
writer.write_all(b"\n").ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::fs::File;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_sort_arg() {
|
||||||
|
let criteria = parse_sort_arg("size:asc,name");
|
||||||
|
assert_eq!(criteria.len(), 2);
|
||||||
|
assert_eq!(criteria[0], (SortColumn::Size, SortDirection::Asc));
|
||||||
|
assert_eq!(criteria[1], (SortColumn::Name, SortDirection::Asc)); // Default for Name is Asc
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sort_defaults() {
|
||||||
|
let criteria = parse_sort_arg("size");
|
||||||
|
assert_eq!(criteria[0], (SortColumn::Size, SortDirection::Dsc)); // Default for Size is Dsc
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_tree() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("test_file.txt");
|
||||||
|
File::create(&file_path)
|
||||||
|
.unwrap()
|
||||||
|
.write_all(b"Hello")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let node = build_tree(temp_dir.path(), false, false);
|
||||||
|
|
||||||
|
assert_eq!(node.entry_type, EntryType::Dir);
|
||||||
|
assert!(!node.children.is_empty());
|
||||||
|
assert_eq!(node.children[0].path, file_path);
|
||||||
|
// Size should be at least 5 bytes
|
||||||
|
assert!(node.children[0].size_bytes >= 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-521
@@ -1,188 +1,23 @@
|
|||||||
use byte_unit::Byte;
|
use clap::Parser;
|
||||||
use chrono::Local;
|
use sized::{run, Args, OutputFormat};
|
||||||
use clap::{CommandFactory, Parser, ValueEnum};
|
|
||||||
use clap_complete::{generate, Shell};
|
|
||||||
use colored::*;
|
|
||||||
use comfy_table::presets::UTF8_FULL;
|
|
||||||
use comfy_table::{Attribute, Cell, CellAlignment, Color, ContentArrangement, Table};
|
|
||||||
use humansize::{format_size, BINARY, DECIMAL};
|
|
||||||
use ignore::WalkBuilder;
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde_json::json;
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::fs::MetadataExt;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use time::macros::format_description;
|
||||||
|
use time::OffsetDateTime;
|
||||||
#[derive(Parser, Debug, Clone)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
#[command(disable_version_flag = true)]
|
|
||||||
struct Args {
|
|
||||||
/// Print version
|
|
||||||
#[arg(short = 'v', long, action = clap::ArgAction::Version)]
|
|
||||||
version: Option<bool>,
|
|
||||||
|
|
||||||
/// Directory to analyze
|
|
||||||
#[arg(default_value = ".")]
|
|
||||||
path: PathBuf,
|
|
||||||
|
|
||||||
/// Minimum size filter (e.g. "10MB", "1gb")
|
|
||||||
#[arg(short = 'm', long)]
|
|
||||||
min_size: Option<String>,
|
|
||||||
|
|
||||||
/// Limit the number of results per table
|
|
||||||
#[arg(short = 'n', long)]
|
|
||||||
number: Option<usize>,
|
|
||||||
|
|
||||||
/// Sort by columns (e.g. "type:asc,size:dsc").
|
|
||||||
/// defaults to size:dsc.
|
|
||||||
/// Columns: name, type, size, blocks.
|
|
||||||
#[arg(long)]
|
|
||||||
sort: Option<String>,
|
|
||||||
|
|
||||||
/// Depth to traverse (0 = only immediate children)
|
|
||||||
#[arg(short, long, default_value = "0")]
|
|
||||||
depth: usize,
|
|
||||||
|
|
||||||
/// Display full absolute paths
|
|
||||||
#[arg(short = 'f', long, conflicts_with = "path_relative")]
|
|
||||||
path_full: bool,
|
|
||||||
|
|
||||||
/// Display paths relative to current directory (default)
|
|
||||||
#[arg(long)]
|
|
||||||
path_relative: bool,
|
|
||||||
|
|
||||||
/// Number of threads to use (defaults to available logical CPUs)
|
|
||||||
#[arg(short = 'j', long = "threads")]
|
|
||||||
threads: Option<usize>,
|
|
||||||
|
|
||||||
/// Generate shell completions
|
|
||||||
#[arg(long, value_enum)]
|
|
||||||
completions: Option<Shell>,
|
|
||||||
|
|
||||||
/// Respect .gitignore and .ignore files
|
|
||||||
#[arg(short = 'i', long)]
|
|
||||||
ignore: bool,
|
|
||||||
|
|
||||||
/// Output format
|
|
||||||
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
|
|
||||||
format: OutputFormat,
|
|
||||||
|
|
||||||
/// Save output to file (optional filename, defaults to timestamped name)
|
|
||||||
#[arg(long, num_args(0..=1), default_missing_value = "_AUTO_")]
|
|
||||||
save: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
|
|
||||||
enum OutputFormat {
|
|
||||||
Text,
|
|
||||||
Csv,
|
|
||||||
Json,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
enum SortColumn {
|
|
||||||
Name,
|
|
||||||
Type,
|
|
||||||
Size,
|
|
||||||
Blocks,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for SortColumn {
|
|
||||||
type Err = String;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_lowercase().as_str() {
|
|
||||||
"name" | "n" => Ok(SortColumn::Name),
|
|
||||||
"type" | "t" => Ok(SortColumn::Type),
|
|
||||||
"size" | "s" => Ok(SortColumn::Size),
|
|
||||||
"blocks" | "b" => Ok(SortColumn::Blocks),
|
|
||||||
_ => Err(format!("Unknown column: {}", s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
enum SortDirection {
|
|
||||||
Asc,
|
|
||||||
Dsc,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for SortDirection {
|
|
||||||
type Err = String;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_lowercase().as_str() {
|
|
||||||
"asc" | "a" => Ok(SortDirection::Asc),
|
|
||||||
"dsc" | "d" | "desc" => Ok(SortDirection::Dsc),
|
|
||||||
_ => Err(format!("Unknown direction: {}", s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
|
||||||
struct PathMetrics {
|
|
||||||
size_bytes: u64,
|
|
||||||
blocks: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct EntryData {
|
|
||||||
path: PathBuf,
|
|
||||||
metrics: PathMetrics,
|
|
||||||
entry_type: String, // "File" or "Dir"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
// Handle completions
|
// Determine output writer logic (moved mostly to main to keep lib clean,
|
||||||
if let Some(shell) = args.completions {
|
// or keep in lib? Plan said simplified main. Let's keep file creation in main usually for CLI apps,
|
||||||
let mut cmd = Args::command();
|
// but the logic for "auto name" is business logic.
|
||||||
let name = cmd.get_name().to_string();
|
// Wait, the lib function `run` takes `writer`. So we need to create the writer here.
|
||||||
generate(shell, &mut cmd, name, &mut std::io::stdout());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure Rayon
|
|
||||||
if let Some(threads) = args.threads {
|
|
||||||
rayon::ThreadPoolBuilder::new()
|
|
||||||
.num_threads(threads)
|
|
||||||
.build_global()
|
|
||||||
.expect("Failed to build thread pool");
|
|
||||||
}
|
|
||||||
|
|
||||||
let target_path = args.path.clone();
|
|
||||||
|
|
||||||
if !target_path.exists() {
|
|
||||||
eprintln!("Error: Path '{}' does not exist.", target_path.display());
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse min_size
|
|
||||||
let min_bytes = if let Some(size_str) = &args.min_size {
|
|
||||||
match Byte::parse_str(size_str, true) {
|
|
||||||
Ok(byte) => byte.as_u64(),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error parsing size '{}': {}", size_str, e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse sort
|
|
||||||
let sort_criteria = parse_sort_arg(args.sort.as_deref().unwrap_or("size:dsc"));
|
|
||||||
|
|
||||||
// Determine output writer
|
|
||||||
let mut writer: Box<dyn Write> = if let Some(path_arg) = &args.save {
|
let mut writer: Box<dyn Write> = if let Some(path_arg) = &args.save {
|
||||||
let output_path = if path_arg.to_string_lossy() == "_AUTO_" {
|
let output_path = if path_arg.to_string_lossy() == "_AUTO_" {
|
||||||
let timestamp = Local::now().format("%Y%m%d-%H%M%S");
|
let format = format_description!("[year][month][day]-[hour][minute][second]");
|
||||||
let dir_name = target_path
|
let timestamp = OffsetDateTime::now_utc().format(format).unwrap();
|
||||||
.file_name()
|
let dir_name = args.path.file_name().unwrap_or_default().to_string_lossy();
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string_lossy();
|
|
||||||
let ext = match args.format {
|
let ext = match args.format {
|
||||||
OutputFormat::Csv => "csv",
|
OutputFormat::Csv => "csv",
|
||||||
OutputFormat::Json => "json",
|
OutputFormat::Json => "json",
|
||||||
@@ -199,349 +34,5 @@ fn main() {
|
|||||||
Box::new(std::io::stdout())
|
Box::new(std::io::stdout())
|
||||||
};
|
};
|
||||||
|
|
||||||
if args.format == OutputFormat::Csv {
|
run(args, &mut writer);
|
||||||
writeln!(writer, "Path,Type,Size(Bytes),Blocks").expect("Failed to write CSV header");
|
|
||||||
}
|
|
||||||
|
|
||||||
if target_path.is_file() {
|
|
||||||
// Single file case
|
|
||||||
let metadata = target_path.metadata().expect("Failed to get metadata");
|
|
||||||
let metrics = PathMetrics {
|
|
||||||
size_bytes: metadata.len(),
|
|
||||||
blocks: metadata.blocks(),
|
|
||||||
};
|
|
||||||
if metrics.size_bytes < min_bytes {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to relative paths unless --path-full is specified
|
|
||||||
let show_relative = !args.path_full;
|
|
||||||
let relative_path = if show_relative {
|
|
||||||
let cwd = std::env::current_dir().unwrap_or_default();
|
|
||||||
target_path
|
|
||||||
.strip_prefix(&cwd)
|
|
||||||
.unwrap_or(&target_path)
|
|
||||||
.display()
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
target_path
|
|
||||||
.canonicalize()
|
|
||||||
.unwrap_or(target_path.clone())
|
|
||||||
.display()
|
|
||||||
.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
print_output(
|
|
||||||
&mut writer,
|
|
||||||
&target_path,
|
|
||||||
&metrics,
|
|
||||||
&[EntryData {
|
|
||||||
path: target_path.clone(),
|
|
||||||
metrics: metrics.clone(),
|
|
||||||
entry_type: "File".to_string(),
|
|
||||||
}],
|
|
||||||
&args,
|
|
||||||
&relative_path,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursive tables
|
|
||||||
process_directory_recursive(
|
|
||||||
&mut writer,
|
|
||||||
&target_path,
|
|
||||||
0,
|
|
||||||
&args,
|
|
||||||
min_bytes,
|
|
||||||
&sort_criteria,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_directory_recursive(
|
|
||||||
writer: &mut dyn Write,
|
|
||||||
path: &PathBuf,
|
|
||||||
current_depth: usize,
|
|
||||||
args: &Args,
|
|
||||||
min_bytes: u64,
|
|
||||||
sort_criteria: &[(SortColumn, SortDirection)],
|
|
||||||
) {
|
|
||||||
// 1. Get immediate children
|
|
||||||
let walker = WalkBuilder::new(path)
|
|
||||||
.standard_filters(false)
|
|
||||||
.hidden(false)
|
|
||||||
.git_ignore(args.ignore)
|
|
||||||
.ignore(args.ignore)
|
|
||||||
.max_depth(Some(1))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let entries: Vec<_> = walker
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|e| e.ok())
|
|
||||||
.filter(|e| e.path() != path) // Exclude root itself
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// 2. Process metrics for them
|
|
||||||
let mut sized_entries: Vec<EntryData> = entries
|
|
||||||
.par_iter()
|
|
||||||
.map(|entry| {
|
|
||||||
let p = entry.path().to_path_buf();
|
|
||||||
let metrics = get_path_metrics(&p, args.ignore);
|
|
||||||
let entry_type = if p.is_file() {
|
|
||||||
"File".to_string()
|
|
||||||
} else {
|
|
||||||
"Dir".to_string()
|
|
||||||
};
|
|
||||||
EntryData {
|
|
||||||
path: p,
|
|
||||||
metrics,
|
|
||||||
entry_type,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let total_metrics = calc_total(&sized_entries);
|
|
||||||
|
|
||||||
// Filter
|
|
||||||
if min_bytes > 0 {
|
|
||||||
sized_entries.retain(|e| e.metrics.size_bytes >= min_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort
|
|
||||||
sized_entries.sort_by(|a, b| compare_entries(a, b, sort_criteria));
|
|
||||||
|
|
||||||
// Limit (per table)
|
|
||||||
if let Some(n) = args.number {
|
|
||||||
if n < sized_entries.len() {
|
|
||||||
sized_entries.truncate(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print this level
|
|
||||||
if current_depth > 0 && args.format == OutputFormat::Text {
|
|
||||||
writeln!(writer, "{}", "-".repeat(60).dimmed()).ok();
|
|
||||||
}
|
|
||||||
// Default to relative paths unless --path-full is specified
|
|
||||||
let show_relative = !args.path_full;
|
|
||||||
let relative_path = if show_relative {
|
|
||||||
let cwd = std::env::current_dir().unwrap_or_default();
|
|
||||||
path.strip_prefix(&cwd)
|
|
||||||
.unwrap_or(path)
|
|
||||||
.display()
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
path.canonicalize()
|
|
||||||
.unwrap_or(path.clone())
|
|
||||||
.display()
|
|
||||||
.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
print_output(
|
|
||||||
writer,
|
|
||||||
path,
|
|
||||||
&total_metrics,
|
|
||||||
&sized_entries,
|
|
||||||
args,
|
|
||||||
&relative_path,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Recurse if needed
|
|
||||||
if current_depth < args.depth {
|
|
||||||
for entry in sized_entries {
|
|
||||||
if entry.entry_type == "Dir" {
|
|
||||||
process_directory_recursive(
|
|
||||||
writer,
|
|
||||||
&entry.path,
|
|
||||||
current_depth + 1,
|
|
||||||
args,
|
|
||||||
min_bytes,
|
|
||||||
sort_criteria,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_path_metrics(path: &PathBuf, ignore: bool) -> PathMetrics {
|
|
||||||
if path.is_file() {
|
|
||||||
let (size, blocks) = path
|
|
||||||
.metadata()
|
|
||||||
.map(|m| (m.len(), m.blocks()))
|
|
||||||
.unwrap_or((0, 0));
|
|
||||||
return PathMetrics {
|
|
||||||
size_bytes: size,
|
|
||||||
blocks,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let walker = WalkBuilder::new(path)
|
|
||||||
.standard_filters(false)
|
|
||||||
.hidden(false)
|
|
||||||
.git_ignore(ignore)
|
|
||||||
.ignore(ignore)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let (size_bytes, blocks) = walker
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|e| e.ok())
|
|
||||||
.par_bridge()
|
|
||||||
.map(|entry| {
|
|
||||||
entry
|
|
||||||
.metadata()
|
|
||||||
.map(|m| (m.len(), m.blocks()))
|
|
||||||
.unwrap_or((0, 0))
|
|
||||||
})
|
|
||||||
.reduce(|| (0, 0), |a, b| (a.0 + b.0, a.1 + b.1));
|
|
||||||
|
|
||||||
PathMetrics { size_bytes, blocks }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calc_total(entries: &[EntryData]) -> PathMetrics {
|
|
||||||
entries.iter().fold(
|
|
||||||
PathMetrics {
|
|
||||||
size_bytes: 0,
|
|
||||||
blocks: 0,
|
|
||||||
},
|
|
||||||
|acc, e| PathMetrics {
|
|
||||||
size_bytes: acc.size_bytes + e.metrics.size_bytes,
|
|
||||||
blocks: acc.blocks + e.metrics.blocks,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compare_entries(
|
|
||||||
a: &EntryData,
|
|
||||||
b: &EntryData,
|
|
||||||
criteria: &[(SortColumn, SortDirection)],
|
|
||||||
) -> Ordering {
|
|
||||||
for (col, dir) in criteria {
|
|
||||||
let order = match col {
|
|
||||||
SortColumn::Name => a.path.file_name().cmp(&b.path.file_name()),
|
|
||||||
SortColumn::Type => a.entry_type.cmp(&b.entry_type),
|
|
||||||
SortColumn::Size => a.metrics.size_bytes.cmp(&b.metrics.size_bytes),
|
|
||||||
SortColumn::Blocks => a.metrics.blocks.cmp(&b.metrics.blocks),
|
|
||||||
};
|
|
||||||
|
|
||||||
if order != Ordering::Equal {
|
|
||||||
return match dir {
|
|
||||||
SortDirection::Asc => order,
|
|
||||||
SortDirection::Dsc => order.reverse(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ordering::Equal
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_sort_arg(s: &str) -> Vec<(SortColumn, SortDirection)> {
|
|
||||||
s.split(',')
|
|
||||||
.filter_map(|part| {
|
|
||||||
let parts: Vec<&str> = part.split(':').collect();
|
|
||||||
if parts.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let col_str = parts[0];
|
|
||||||
let col = SortColumn::from_str(col_str).ok()?;
|
|
||||||
|
|
||||||
let dir = if parts.len() > 1 {
|
|
||||||
SortDirection::from_str(parts[1]).unwrap_or(SortDirection::Dsc)
|
|
||||||
} else {
|
|
||||||
match col {
|
|
||||||
SortColumn::Name | SortColumn::Type => SortDirection::Asc,
|
|
||||||
_ => SortDirection::Dsc,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Some((col, dir))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_output(
|
|
||||||
writer: &mut dyn Write,
|
|
||||||
path: &PathBuf,
|
|
||||||
metrics: &PathMetrics,
|
|
||||||
entries: &[EntryData],
|
|
||||||
args: &Args,
|
|
||||||
relative_path: &str,
|
|
||||||
) {
|
|
||||||
match args.format {
|
|
||||||
OutputFormat::Text => {
|
|
||||||
writeln!(writer, "\n{}", relative_path.bold().blue()).ok();
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"Total size: {} / {} ({} blocks)",
|
|
||||||
format_size(metrics.size_bytes, BINARY).bold().green(),
|
|
||||||
format_size(metrics.size_bytes, DECIMAL).bold().green(),
|
|
||||||
metrics.blocks
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
let mut table = Table::new();
|
|
||||||
table
|
|
||||||
.load_preset(UTF8_FULL)
|
|
||||||
.set_content_arrangement(ContentArrangement::Dynamic)
|
|
||||||
.set_header(vec![
|
|
||||||
Cell::new("Name").add_attribute(Attribute::Bold),
|
|
||||||
Cell::new("Type").add_attribute(Attribute::Bold),
|
|
||||||
Cell::new("Size (Bin)")
|
|
||||||
.add_attribute(Attribute::Bold)
|
|
||||||
.set_alignment(CellAlignment::Right),
|
|
||||||
Cell::new("Size (Bytes)")
|
|
||||||
.add_attribute(Attribute::Bold)
|
|
||||||
.set_alignment(CellAlignment::Right),
|
|
||||||
Cell::new("Blocks")
|
|
||||||
.add_attribute(Attribute::Bold)
|
|
||||||
.set_alignment(CellAlignment::Right),
|
|
||||||
]);
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
let name = entry.path.file_name().unwrap_or_default().to_string_lossy();
|
|
||||||
let color = if entry.entry_type == "Dir" {
|
|
||||||
Color::Blue
|
|
||||||
} else {
|
|
||||||
Color::Cyan
|
|
||||||
};
|
|
||||||
|
|
||||||
table.add_row(vec![
|
|
||||||
Cell::new(name).fg(color),
|
|
||||||
Cell::new(&entry.entry_type).fg(Color::Yellow),
|
|
||||||
Cell::new(format_size(entry.metrics.size_bytes, BINARY))
|
|
||||||
.fg(Color::Green)
|
|
||||||
.set_alignment(CellAlignment::Right),
|
|
||||||
Cell::new(format_size(entry.metrics.size_bytes, DECIMAL))
|
|
||||||
.fg(Color::Green)
|
|
||||||
.set_alignment(CellAlignment::Right),
|
|
||||||
Cell::new(entry.metrics.blocks)
|
|
||||||
.fg(Color::Green)
|
|
||||||
.set_alignment(CellAlignment::Right),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
writeln!(writer, "{}", table).ok();
|
|
||||||
}
|
|
||||||
OutputFormat::Csv => {
|
|
||||||
// Check if we need to print header? Csv writer handles it if we tell it?
|
|
||||||
// Simple flatten: Path,Type,Size,Blocks
|
|
||||||
// But we have entries list.
|
|
||||||
for entry in entries {
|
|
||||||
writeln!(
|
|
||||||
writer,
|
|
||||||
"{},{},{},{}",
|
|
||||||
entry.path.display(),
|
|
||||||
entry.entry_type,
|
|
||||||
entry.metrics.size_bytes,
|
|
||||||
entry.metrics.blocks
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OutputFormat::Json => {
|
|
||||||
// NDJSON report per directory
|
|
||||||
let report = json!({
|
|
||||||
"path": path,
|
|
||||||
"total_size": metrics.size_bytes,
|
|
||||||
"blocks": metrics.blocks,
|
|
||||||
"entries": entries
|
|
||||||
});
|
|
||||||
writeln!(writer, "{}", serde_json::to_string(&report).unwrap()).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use assert_cmd::prelude::*;
|
||||||
|
use predicates::prelude::*;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Command;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_cli_run() {
|
||||||
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_sized"));
|
||||||
|
cmd.arg("--version")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::str::contains("sized 0.2.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_run_on_directory() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("test_file.txt");
|
||||||
|
File::create(&file_path)
|
||||||
|
.unwrap()
|
||||||
|
.write_all(b"Hello")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_sized"));
|
||||||
|
cmd.arg(temp_dir.path())
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::str::contains("test_file.txt")); // Basic check
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_json_output() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let file_path = temp_dir.path().join("data.json");
|
||||||
|
File::create(&file_path).unwrap().write_all(b"{}").unwrap();
|
||||||
|
|
||||||
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_sized"));
|
||||||
|
cmd.arg(temp_dir.path())
|
||||||
|
.arg("--format")
|
||||||
|
.arg("json")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::str::contains("\"entry_type\":\"File\""))
|
||||||
|
.stdout(predicate::str::contains("\"path\":"));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user