topnfiles-4.1.12/.cargo_vcs_info.json0000644000000001361046102023000131460ustar { "git": { "sha1": "7f36eb3c5a807cc4c2fd19783cd8baf63bae1e98" }, "path_in_vcs": "" }topnfiles-4.1.12/.gitignore000064400000000000000000000000151046102023000137000ustar 00000000000000target *.swp topnfiles-4.1.12/BUILDING.txt000064400000000000000000000014531046102023000136550ustar 00000000000000Install rust. Install also some Cargo extensions: cargo install cargo-generate-rpm cargo install cargo-deb Then run one of these: To get a simple executable in target/release/topnfiles cargo build --release Then, to get a Red Hat/Fedora family rpm file in target/generate-rpm: cargo generate-rpm To get a Debian family deb file in target/release/debian: cargo deb Building in a podman container ------------------------------ To build in a very clean environment, you may build in a container using podman (docker may also work). Having installed podman, from this directory, you may run the following: cd packaging ./build_in_podman_container.sh DESTINATIONDIRECTORY The resulting .deb/.rpm files should work well on just about any Debian or Red Hat derived Linux system, even rather old ones. topnfiles-4.1.12/CHANGELOG.md000064400000000000000000000063461046102023000135360ustar 00000000000000# Changelog 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.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [4.1.12] - 2026-02-06 ### Changed - #13: More Debian packaging work: The debian/ subdirectory was causing trouble, so it is gone. If the crate gets included in Debian, it will be through a debian directory in the debcargo-conf repository. ## [4.1.11] - 2026-02-01 ### Changed - Versioning consistency across Cargo.lock, Cargo.toml, and Debian-related files. ## [4.1.10] - 2026-01-31 ### Changed - Readme fix. - Now passes pedantic Clippy. ## [4.1.9] - 2025-09-08 ### Changed - #13: Use less bleeding edge syntax, so the code builds on Debian Sid using Debian's in-box Rust. - #13: First attempt at making Debian-buildable with "debian" subdirectory. ## [4.1.8] - 2025-09-06 ### Changed - Clearer readme text about the "j" output modifier. - Make rpmlint happy about package description format. ## [4.1.7] - 2025-09-06 ### Changed - Move to new change log format. ## [4.1.6] - 2025-09-05 ### Changed - More packaging work. - Help output stringency ## [4.1.5] - 2025-09-04 ### Fixed - Fix man page section (8 to 1). - Fix install part of topnfiles.spec. ## [4.1.3] - 2025-09-02 ### Changed - More steps towards easier RPM packaging: Loosen version dependency for tempfile (the later tempfile 3.21 is only of interest on Windows). ## [4.1.2] - 2025-08-31 ### Changed - More steps towards easier RPM packaging. ## [4.1.1] - 2025-08-31 ### Changed - Steps towards easier RPM packaging. ## [4.1.0] - 2025-08-30 ### Added - Make it possible to specify more than one start directory (#10). ## [4.0.0] - 2025-08-30 ### Changed - Breaking change: Default output now includes ISO timestamp. - Update dependencies. - Separate test into separate file, and extend with more tests. ## [3.0.0] - 2025-08-17 ### Added - Add --exclude and --include arguments. ### Changed - Breaking change: The path to start in must be provided (in order to prevent confusing situations where an --include argument is used while forgetting to provide a value for --include). ## [2.3.0] - 2025-08-17 ### Added - More capable handling of odd characters. ## [2.2.1] - 2025-08-10 ### Fixed - Fix normal output with regards to newlines. ## [2.2.0] - 2025-08-10 ### Added - Add 'j' output option to produce JSON output. ### Changed - Restructured code for future increased testability. ## [2.1.0] - 2025-08-09 ### Added - Add '0' output option to mimic the find tool's print0 option. ### Changed - Make output line order more predictable, and make man page and README more precise about the order of lines in result output. - Make test code more reliable. ## [2.0.0] - 2025-08-02 ### Added - Can now sort on file size. ### Changed - Breaking change: Argument "parameter" has been renamed to "attribute". - Will now refuse to work from weird argument combinations, unless "--weird" is used. ## [1.1.0] - 2025-07-31 ### Fixed - Fix TOCTOU which could yield inconsistent output. ## [1.0.0] - 2025-07-28 ### Changed - First public version. ## [0.9.0] - 2025-07-27 ### Changed - Version for testing. topnfiles-4.1.12/Cargo.lock0000644000000502451046102023000111270ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.61.2", ] [[package]] name = "anyhow" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cc" version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "num-traits", "windows-link", ] [[package]] name = "clap" version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_lex" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "env_filter" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", "jiff", "log", ] [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[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]] name = "iana-time-zone" version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" 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]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", "serde_core", ] [[package]] name = "jiff-static" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "js-sys" version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "linux-raw-sys" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustix" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.61.2", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", "serde", "serde_core", "zmij", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "supports-unicode" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", "windows-sys 0.61.2", ] [[package]] name = "terminal_size" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix", "windows-sys 0.60.2", ] [[package]] name = "topnfiles" version = "4.1.12" dependencies = [ "anyhow", "chrono", "clap", "env_logger", "log", "regex", "serde", "serde_json", "supports-unicode", "tempfile", "walkdir", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[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]] name = "wasm-bindgen" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[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", ] [[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", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zmij" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" topnfiles-4.1.12/Cargo.toml0000644000000061501046102023000111460ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" name = "topnfiles" version = "4.1.12" authors = ["Troels Arvin "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Finds newest/oldest/smallest/largest files in a directory structure" readme = "README.md" keywords = [ "cleanup", "cli", "filesystem", "utility", ] categories = ["command-line-utilities"] license = "MIT" repository = "https://gitlab.com/troelsarvin/topnfiles" [package.metadata.deb] section = "utility" priority = "optional" assets = [ [ "CHANGELOG.md", "usr/share/doc/topnfiles/CHANGELOG.md", "644", ], [ "README.md", "usr/share/doc/topnfiles/README.md", "644", ], [ "LICENSE.txt", "usr/share/doc/topnfiles/LICENSE.txt", "644", ], [ "packaging/usr/share/man/man1/topnfiles.1", "usr/share/man/man1/topnfiles.1", "644", ], [ "target/release/topnfiles", "usr/bin/topnfiles", "755", ], ] [[package.metadata.generate-rpm.assets]] source = "CHANGELOG.md" dest = "/usr/share/doc/topnfiles/CHANGELOG.md" mode = "0644" config = false doc = true user = "root" group = "root" [[package.metadata.generate-rpm.assets]] source = "README.md" dest = "/usr/share/doc/topnfiles/README.md" mode = "0644" config = false doc = true user = "root" group = "root" [[package.metadata.generate-rpm.assets]] source = "LICENSE.txt" dest = "/usr/share/doc/topnfiles/LICENSE.txt" mode = "0644" config = false doc = true user = "root" group = "root" [[package.metadata.generate-rpm.assets]] source = "packaging/usr/share/man/man1/topnfiles.1" dest = "/usr/share/man/man1/topnfiles.1" mode = "0644" config = false doc = false user = "root" group = "root" [[package.metadata.generate-rpm.assets]] source = "target/release/topnfiles" dest = "/usr/bin/topnfiles" mode = "0755" config = false doc = false user = "root" group = "root" [[bin]] name = "topnfiles" path = "src/main.rs" [dependencies.anyhow] version = "1.0" [dependencies.chrono] version = "0.4" features = ["clock"] default-features = false [dependencies.clap] version = "4" features = [ "cargo", "wrap_help", ] [dependencies.env_logger] version = "0.11" [dependencies.log] version = "0.4" [dependencies.regex] version = "1" [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.serde_json] version = "1.0" [dependencies.supports-unicode] version = "3.0.0" [dependencies.tempfile] version = "3.24" [dependencies.walkdir] version = "2.5" [lints.clippy] pedantic = "deny" perf = "deny" style = "deny" [lints.rust] unsafe_code = "forbid" [profile.release] lto = "fat" codegen-units = 1 overflow-checks = true topnfiles-4.1.12/Cargo.toml.orig000064400000000000000000000040241046102023000146030ustar 00000000000000[package] name = "topnfiles" version = "4.1.12" edition = "2024" license = "MIT" description = "Finds newest/oldest/smallest/largest files in a directory structure" categories = ["command-line-utilities"] authors = ["Troels Arvin "] keywords = ["cleanup", "cli", "filesystem", "utility"] repository = "https://gitlab.com/troelsarvin/topnfiles" [lints.rust] unsafe_code = "forbid" [lints.clippy] pedantic = "deny" perf = "deny" style = "deny" [profile.release] overflow-checks = true codegen-units = 1 lto = "fat" [package.metadata.deb] section = "utility" priority = "optional" assets = [ ["CHANGELOG.md", "usr/share/doc/topnfiles/CHANGELOG.md", "644"], ["README.md", "usr/share/doc/topnfiles/README.md", "644"], ["LICENSE.txt", "usr/share/doc/topnfiles/LICENSE.txt", "644"], ["packaging/usr/share/man/man1/topnfiles.1", "usr/share/man/man1/topnfiles.1", "644"], ["target/release/topnfiles", "usr/bin/topnfiles", "755"], ] [package.metadata.generate-rpm] assets = [ { source="CHANGELOG.md", dest="/usr/share/doc/topnfiles/CHANGELOG.md", mode="0644", config=false, doc=true, user="root", group="root" }, { source="README.md", dest="/usr/share/doc/topnfiles/README.md", mode="0644", config=false, doc=true, user="root", group="root" }, { source="LICENSE.txt", dest="/usr/share/doc/topnfiles/LICENSE.txt", mode="0644", config=false, doc=true, user="root", group="root" }, { source="packaging/usr/share/man/man1/topnfiles.1", dest="/usr/share/man/man1/topnfiles.1", mode="0644", config=false, doc=false, user="root", group="root" }, { source="target/release/topnfiles", dest="/usr/bin/topnfiles", mode="0755", config=false, doc=false, user="root", group="root" }, ] [dependencies] anyhow = "1.0" chrono = { version = "0.4", default-features = false, features = ["clock"] } clap = { version = "4", features = ["cargo", "wrap_help"] } env_logger = "0.11" log = "0.4" regex = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" supports-unicode = "3.0.0" tempfile = "3.24" walkdir = "2.5" topnfiles-4.1.12/Dockerfile000064400000000000000000000005331046102023000137070ustar 00000000000000# Being used by the packaging/build_in_podman_container.sh script ARG APPNAME topnfiles FROM rust:1.89-bullseye COPY . . RUN apt-get update RUN <" argument determines how topnfiles pick resulting directory entries. Possible values for "--return" are: - youngest - oldest - smallest - largest The result set is printed like this: If using "-r youngest", youngest qualifying files are printed last. If using "-r oldest", oldest qualifying files are printed last. If using "-r smallest ", smallest qualifying files are printed last. If using "-r largest", largest qualifying files are printed last. ## Output Output format is governed by the "-o"/"--output" parameter, defaulting to human readable output. ### Human readable output For this output mode, for each directory entry found, a line is printed to stdout, in a way which is human readable: Problematic characters are replaced by Unicode control pictures, or are caret escaped, depending on the capabilities of the environment. One implication of this is that the one-line-per-path rule is upheld. The exact format of human readable output may change between versions. File metadata elements are separated by a space. Element order is as follows (to the extent the elements are requested in the --output argument): - Size (in bytes) - ISO timestamp - Unix timestamp - File path ### Machine readable output The "j" output option (which may not be combined with other output modifiers) results in JSON output. If using the "0" output option (which may also not be combined with other output modifiers), each resulting path is suffixed with a NULL (ASCII 0) character, instaed of a newline, similar to the *find* tool's -print0 option. In this situation, "odd" characters are not replace with the U+FFFD unicode character. ## See also Source code is available at https://gitlab.com/troelsarvin/topnfiles Compiled packages may be available at https://troels.arvin.dk/topnfiles/ topnfiles-4.1.12/packaging/_copy_artefacts_from_container.sh000075500000000000000000000003761046102023000224370ustar 00000000000000#!/bin/bash # This file is a helper script for build_in_podman_container.sh in # the directory above. mountpoint=$(podman mount $cid) cp "${mountpoint}/target/debian/"*.deb "${destination}" cp "${mountpoint}/target/generate-rpm/"*.rpm "${destination}" topnfiles-4.1.12/packaging/build_in_podman_container.sh000075500000000000000000000026271046102023000213730ustar 00000000000000#!/bin/bash # Build image from the official rust image on a very # conservative base image (operating system wize): thisfile=$(basename $0) # Be defensive set -u # If you try to access an undefined variable, that is an error. set -o pipefail # If any command in a pipeline returns a non-zero exit code, # the return code of the entire pipeline is the exit code of # the last failed command. err () { msg="$1" echo "Error: $1" >&2 exit 1 } usage () { echo "Usage:" echo " $thisfile destinationfolder" } [ $# -ne 1 ] && err "Required destination folder argument missing" if [ "$1" == '-h' ] || [ "$1" == '--help' ]; then usage fi export destination="$1" [ -d "$destination" ] || err "Destination $destination does not exist" which podman > /dev/null 2>&1 || err "Podman needs to be installed" img=$(podman build --quiet ..) if [ $? == 0 ]; then image_built=yes else image_built=no fi echo "image_built: $image_built" echo "img: $img" if [ "$image_built" == no ]; then if [ -n "$img" ] && [ -f "$img" ]; then rm -f "$img" fi echo "Failed to build image; exiting" >&2 exit 1 fi # Start a container from the image: export cid=$(podman run -d $img sleep infinity) && \ podman unshare ../packaging/_copy_artefacts_from_container.sh && \ podman kill $cid > /dev/null [ "$image_built" == yes ] && podman image rm --force "$img" topnfiles-4.1.12/packaging/usr/share/man/man1/topnfiles.1000064400000000000000000000073431046102023000212560ustar 00000000000000.TH TOPNFILES 8 2025-07-27 .SH NAME topnfiles \- Find top-n files based on file metadata such as modification time .SH SYNOPSIS .B topnfiles .RI [ options " .\|.\|.\&]" .IR ... .SH DESCRIPTION .B topnfiles Purpose: In a large directory structure, identify those files which - have least/most recently been used, or - are smallest/largest .PP For each directory entry in the result set, a line is printed to stdout. .PP The order of output lines follows requested search order: - If using "-r youngest", youngest qualifying files are printed last. - If using "-r oldest", oldest qualifying files are printed last. - If using "-r largest", largest qualifying files are printed last. - If using "-r smallest ", smallest qualifying files are printed last. .PP The elements of the line are controlled by the --output parameter. Element order is as follows (to the extent the elements are requested in the --output argument): - Size (in bytes) - ISO timestamp - Unix timestamp - File path .SH OPTIONS .TP \fB\-a\fR, \fB\-\-attribute\fR Which time-like file attribute to sort on, when time based sorting is asked for: accessed, created, or modified. A combination of "--attribute accessed" and "--directories" is refused (unless --weird is used). No matter what, a combination of an attribute argument and "--return largest/smallest" is refused. .TP \fB\-d\fR, \fB\-\-directories\fR Include directories, not only files. .TP \fB\-e\fR, \fB\-\-exclude\fR \fIFILTER\fR Only consider files where the path does NOT match the specified regular expression. See FILTERING for more on this. .TP \fB\-i\fR, \fB\-\-include\fR \fIFILTER\fR Only consider files where the path matches the specified regular expression. See FILTERING for more on this. .TP \fB\-n\fR, \fB\-\-number\fR \fIinteger\fR The number of files to return; defaults to 10. .TP \fB\-o\fR, \fB\-\-output\fR \fIFLAGS\fR Which file metadata to include in output. Options: 0=print0, b=size(bytes), i=ISO time, j=JSON, p=human readable path, u=unixtime. Defaults to "ip". The default output format may change between versions. If you depend on the output being in a particular format, stick to the JSON or print0 formats. .br In the "p" case, a line is printed per path. Paths are made print-friendly: Problematic characters like newlines and the bell character are replaced with the Unicode control pictures or caret escaped charaters, depending on the capabilities of the environment. This guarantees, that the one-path-per-line rule is upheld. .br "0" means that instead of separating with newlines, the NULL character will separate each printed path, like the "find" tool's -print0 option. "0" may not be combined with other output modifiers. .TP \fB\-r\fR, \fB\-\-return\fR Return oldest, youngest (default), largest, or smallest. .TP \fB\-x\fR, \fB\-\-xdev\fR Within each start directory: Stay within one mountpoint, i.e. do not descend into other filesystems. .TP \fB\-h\fR, \fB\-\-help\fR Display help and exit. .TP \fB\-v\fR, \fB\-\-version\fR Display version information and exit. .SH FILTERING If a path is match with both the --include and the --exclude pattern, it will not be considered. .PP For the --exclude and --include arguments, the regular expresion dialect is that of the 'regex' Rust crate: https://docs.rs/regex/latest/regex/#syntax .br If you want the patterns to match case insensitively, use the (?i) prefix to the pattern, e.g.: .br --include '(:i)\\./my/pattern' .SH NOTES topnfiles will (unless you use the --weird argument) refuse to work, if you pass start directories which are the same (after path canonicalization). But it does not try to detect, if you pass directories where one is a sub-directory of the other; in this case, a directory entry may appear more than one time in output. .SH BUGS No known bugs. topnfiles-4.1.12/rust2rpm.toml000064400000000000000000000006261046102023000144130ustar 00000000000000[package] description = "Recursively finds n most/least recently modified or smallest/largest files in a directory structure." doc-files.exclude = ["BUILDING.txt"] extra-files = ["%{_mandir}/man1/%{name}.1*"] source-url = "%{url}/-/archive/%{version}/%{name}-%{version}.tar.gz" [scripts] install.post = [ "install -Dpm 0644 packaging%{_mandir}/man1/%{name}.1 %{buildroot}%{_mandir}/man1/%{name}.1", ] topnfiles-4.1.12/src/main.rs000064400000000000000000000704321046102023000140030ustar 00000000000000#![warn(clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated)] #[cfg(test)] mod test; // ======================================================= // Imports // ======================================================= use anyhow::{Context, Result, bail}; use chrono::Local; use clap::{Arg, Command, crate_description, crate_version, parser::ArgMatches}; use env_logger::Builder; use log::{LevelFilter, debug, warn}; use regex::Regex; use serde::Serialize; use std::cmp::{Ordering, Reverse}; use std::collections::{BinaryHeap, HashSet}; use std::env::set_current_dir; use std::ffi::OsString; use std::fmt::Debug; use std::fs::{Metadata, canonicalize}; use std::io::Write; use std::str::FromStr; use std::time::SystemTime; use walkdir::{DirEntry, WalkDir}; // ======================================================= // Defaults // ======================================================= const DEFAULT_DIRECTORIES: bool = false; const DEFAULT_LOGLEVEL: LevelFilter = LevelFilter::Info; const DEFAULT_NUMBER: &str = "10"; const DEFAULT_OUTPUT_COMBO: &str = "ip"; // By default we output file ISO timestamp and path const DEFAULT_RETURN: &str = "youngest"; const DEFAULT_TIME_ATTRIBUTE: TimeAttribute = TimeAttribute::Modified; const DEFAULT_WEIRD: bool = false; const DEFAULT_XDEV: bool = true; // ======================================================= // Types and conversions // ======================================================= // Used when deciding on what file attribute to order on #[derive(Debug, PartialEq)] enum TimeAttribute { Accessed, Created, Modified, Unset, } impl FromStr for TimeAttribute { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s { "accessed" => Ok(Self::Accessed), "created" => Ok(Self::Created), "modified" => Ok(Self::Modified), "" => Ok(Self::Unset), _ => bail!("Invalid time attribute '{s}'"), } } } // Used for handling options regarding result output #[derive(Debug, Eq, Hash, PartialEq)] enum OutputItem { Nulls, Json, Path, SizeBytes, TimeISO, TimeUnix, } #[derive(Eq, Debug, PartialEq)] struct OutputCombo { output_items: HashSet, } impl Default for OutputCombo { fn default() -> Self { Self::from_str(DEFAULT_OUTPUT_COMBO).expect("Invalid default") } } impl FromStr for OutputCombo { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let mut hs = HashSet::default(); let letters = s.chars(); for letter in letters { let item = match letter { '0' => OutputItem::Nulls, 'b' => OutputItem::SizeBytes, 'p' => OutputItem::Path, 'i' => OutputItem::TimeISO, 'j' => OutputItem::Json, 'u' => OutputItem::TimeUnix, _ => bail!("Unknown output flag '{letter}'"), }; hs.insert(item); } Ok(Self { output_items: hs }) } } impl OutputCombo { fn contains(&self, value: &OutputItem) -> bool { self.output_items.contains(value) } } // Handling of: Are we looking for newest or oldest files? #[derive(Debug, PartialEq)] enum ResultOrder { Oldest, Youngest, Largest, Smallest, } impl FromStr for ResultOrder { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s { "oldest" => Ok(Self::Oldest), "youngest" => Ok(Self::Youngest), "largest" => Ok(Self::Largest), "smallest" => Ok(Self::Smallest), _ => bail!("Unknown result order '{s}'"), } } } // Handling of configuration #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] struct Cfg { directories: bool, exclude: Option, include: Option, number: usize, output_combo: OutputCombo, result_order: ResultOrder, startdirs: Vec, time_attribute: TimeAttribute, unicode_supported: bool, weird: bool, xdev: bool, } trait OrderingSystemTime: Ord + From { fn from_timeattr(st: SystemTime) -> Self; } // For determining the oldest files, we need a heap where the // newest (highest) item is removed by pop(), so that the smallest // (oldest) items are kept. // So we use a max-heap which in Rust is done with BinaryHeap (which // per default is a max-heap). #[derive(Debug, Eq, PartialEq, PartialOrd, Ord)] struct SystemTimeForOldest { timeattr: SystemTime, } impl OrderingSystemTime for SystemTimeForOldest { fn from_timeattr(st: SystemTime) -> Self { Self { timeattr: st } } } impl From for SystemTimeForOldest { fn from(st: SystemTime) -> Self { Self { timeattr: st } } } impl From for SystemTime { fn from(s: SystemTimeForOldest) -> Self { s.timeattr } } // For determining the youngest files, we need a heap where the // oldest (with lowest time value) item is removed by pop(), so // that the largest (youngest) items are kept. // So we use a min-heap which in Rust is done with BinaryHeap (which // per default is a max-heap) with Reverse elements. #[derive(Debug, Eq, PartialEq, PartialOrd, Ord)] struct SystemTimeForYoungest { timeattr: Reverse, } impl OrderingSystemTime for SystemTimeForYoungest { fn from_timeattr(st: SystemTime) -> Self { Self { timeattr: Reverse(st), } } } impl From for SystemTimeForYoungest { fn from(st: SystemTime) -> Self { Self { timeattr: Reverse(st), } } } impl From for SystemTime { fn from(s: SystemTimeForYoungest) -> Self { s.timeattr.0 } } type FileSize = u64; trait OrderingFileSize: Ord + From { fn from_filesize(s: FileSize) -> Self; } // For determining the largest files, we need a heap where the // smallest item is removed by pop(), so that the largest items are kept. // So we use a min-heap which in Rust is done with BinaryHeap (which // per default is a max-heap) with Reverse elements. #[derive(Debug, Eq, PartialEq, PartialOrd, Ord)] struct FileSizeForLargest { filesize: Reverse, } impl OrderingFileSize for FileSizeForLargest { fn from_filesize(s: FileSize) -> Self { Self { filesize: Reverse(s), } } } impl From for FileSizeForLargest { fn from(s: FileSize) -> Self { Self { filesize: Reverse(s), } } } impl From for FileSize { fn from(s: FileSizeForLargest) -> Self { s.filesize.0 } } // For determining the smallest files, we need a heap where the // largest item is removed by pop(), so that the largest items are kept. // So we use a max-heap which in Rust is BinaryHeap. #[derive(Debug, Eq, PartialEq, PartialOrd, Ord)] struct FileSizeForSmallest { filesize: FileSize, } impl OrderingFileSize for FileSizeForSmallest { fn from_filesize(filesize: FileSize) -> Self { Self { filesize } } } impl From for FileSizeForSmallest { fn from(filesize: FileSize) -> Self { Self { filesize } } } impl From for FileSize { fn from(s: FileSizeForSmallest) -> Self { s.filesize } } // Structure recording directory entry information in a way where // it sorts it self against other TimeOrderedScoreBoardEntry data as being newer/older, // based on which OrderingSystemTime type is being used to instantiate // the struct. #[derive(Debug)] struct TimeOrderedScoreBoardEntry { timeattr: T, path: OsString, size_b: FileSize, } impl PartialOrd for TimeOrderedScoreBoardEntry { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for TimeOrderedScoreBoardEntry { fn cmp(&self, other: &Self) -> Ordering { self.timeattr.cmp(&other.timeattr) } } impl PartialEq for TimeOrderedScoreBoardEntry { fn eq(&self, other: &Self) -> bool { self.timeattr == other.timeattr } } impl Eq for TimeOrderedScoreBoardEntry {} type TimeOrderedScoreBoard = BinaryHeap>; // Structure recording directory entry information in a way where // it sorts it self against other ScoreBoardEntry data as being smaller/larger, // based on which OrderingFileSize type is being used to instantiate // the struct. #[derive(Debug)] struct SizeOrderedScoreBoardEntry { timeattr: SystemTime, path: OsString, size_b: T, } impl PartialOrd for SizeOrderedScoreBoardEntry { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for SizeOrderedScoreBoardEntry { fn cmp(&self, other: &Self) -> Ordering { self.size_b.cmp(&other.size_b) } } impl PartialEq for SizeOrderedScoreBoardEntry { fn eq(&self, other: &Self) -> bool { self.size_b == other.size_b } } impl Eq for SizeOrderedScoreBoardEntry {} type SizeOrderedScoreBoard = BinaryHeap>; // Structure recording directory entry information which is not // to be sorted on, but which is to be printed. #[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] struct OutputScoreBoardEntry { timeattr: SystemTime, path: String, size_b: FileSize, } impl From> for OutputScoreBoardEntry where SystemTime: From, { fn from(se: TimeOrderedScoreBoardEntry) -> Self { Self { timeattr: se.timeattr.into(), path: se.path.to_string_lossy().to_string(), size_b: se.size_b, } } } impl From> for OutputScoreBoardEntry where FileSize: From, { fn from(se: SizeOrderedScoreBoardEntry) -> Self { Self { timeattr: se.timeattr, path: se.path.to_string_lossy().to_string(), size_b: se.size_b.into(), } } } #[derive(Debug, Serialize)] struct OutputScoreBoard(Vec); impl From> for OutputScoreBoard where SystemTime: From, { fn from(mut sb: TimeOrderedScoreBoard) -> Self { let mut out = Vec::new(); let mut entry_out: OutputScoreBoardEntry; while let Some(entry) = sb.pop() { entry_out = entry.into(); out.push(entry_out); } Self(out) } } impl From> for OutputScoreBoard where FileSize: From, { fn from(mut sb: SizeOrderedScoreBoard) -> Self { let mut out = Vec::new(); let mut entry_out: OutputScoreBoardEntry; while let Some(entry) = sb.pop() { entry_out = entry.into(); out.push(entry_out); } Self(out) } } impl IntoIterator for OutputScoreBoard { type Item = OutputScoreBoardEntry; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } // ======================================================= // Program initialization / boiler plate // ======================================================= // Catch-all for errors which have not otherwise "manually" been caught fn main() { if let Err(e) = begin() { bailout(&e.to_string()); } } // Args handling, configuration setup, sanity checks, and initialization // of program logic. fn begin() -> Result<()> { if std::env::var("RUST_LOG").is_err() { let mut builder = Builder::from_default_env(); builder.filter(None, DEFAULT_LOGLEVEL).init(); } else { env_logger::init(); } let args = parse_args(); // unwrap()s are OK here, because Clap has ensured we get a value let exclude = match args.get_one::("exclude") { Some(exclude_str) => Some( Regex::new(exclude_str) .context("Invalid regular expression for the --exclude argument")?, ), None => None, }; let include = match args.get_one::("include") { Some(include_str) => Some( Regex::new(include_str) .context("Invalid regular expression for the --include argument")?, ), None => None, }; let output_combo_str = args.get_one::("output").unwrap().as_str(); let output_combo = OutputCombo::from_str(output_combo_str)?; let result_order_str = args.get_one::("return").unwrap().as_str(); let result_order = ResultOrder::from_str(result_order_str)?; let start_strings = args.get_many::("startdirs").unwrap(); let startdirs: Vec = start_strings.map(|s| (*s).clone().into()).collect(); let time_attribute_str = args.get_one::("attribute").unwrap().as_str(); let time_attribute = TimeAttribute::from_str(time_attribute_str)?; let mut cfg = Cfg { // unwrap()s are OK here, because Clap has ensured we get a value directories: *args .get_one::("directories") .unwrap_or(&DEFAULT_DIRECTORIES), exclude, include, number: *args.get_one::("number").unwrap(), output_combo, result_order, startdirs, time_attribute, weird: *args.get_one::("weird").unwrap_or(&DEFAULT_WEIRD), unicode_supported: false, xdev: *args.get_one::("xdev").unwrap_or(&DEFAULT_XDEV), }; sanity_check(&cfg)?; // We are past sanity check, so if cfg.time_attribute is None, we can set // it to the default value. if cfg.time_attribute == TimeAttribute::Unset { cfg.time_attribute = DEFAULT_TIME_ATTRIBUTE; } debug!("cfg: {cfg:?}"); // Determine, if we can use fancy characters in output cfg.unicode_supported = supports_unicode::on(supports_unicode::Stream::Stdout); let os = build_output_scoreboard(&cfg)?; let out_bytes: Vec = build_output_bytes(&cfg, os)?; let mut stdout = std::io::stdout().lock(); stdout.write_all(&out_bytes)?; Ok(()) } fn build_output_scoreboard(cfg: &Cfg) -> Result { let os = match cfg.result_order { ResultOrder::Oldest => recurse_for_time::(cfg)?.into(), ResultOrder::Youngest => recurse_for_time::(cfg)?.into(), ResultOrder::Largest => recurse_for_size::(cfg)?.into(), ResultOrder::Smallest => recurse_for_size::(cfg)?.into(), }; Ok(os) } // ====================================================== // Program logic. // ====================================================== fn recurse_for_time( cfg: &Cfg, ) -> anyhow::Result, anyhow::Error> where SystemTime: From, { let mut scoreboard = TimeOrderedScoreBoard::::with_capacity(cfg.number); let mut access_problem_seen = false; for startdir in &cfg.startdirs { for entry in WalkDir::new(startdir) .same_file_system(cfg.xdev) .into_iter() .filter_map(|r| r.map_err(|_| access_problem_seen = true).ok()) .filter(|e| pathfilter(cfg, e)) { if entry.path().is_dir() && !cfg.directories { debug!( "Skipping path' {}' because it is a directory", entry.path().display() ); continue; } match entry.metadata() { Ok(metadata) => { let filetime = get_time_attribute(cfg, &metadata)?; let to_add = TimeOrderedScoreBoardEntry:: { timeattr: OrderingSystemTime::from_timeattr(filetime), path: entry.path().as_os_str().to_os_string().clone(), size_b: metadata.len(), }; debug!("Adding to scoreboard: {to_add:?}"); scoreboard.push(to_add); if scoreboard.len() > cfg.number { let removed = scoreboard.pop(); debug!("Removed from scoreboard: {removed:?}"); } } Err(e) => { debug!( "Could not get metadata for path '{}': {e}", entry.path().display() ); } } } } if access_problem_seen { warn!("At least one inaccessible directory was skipped"); } Ok(scoreboard) } fn recurse_for_size( cfg: &Cfg, ) -> anyhow::Result, anyhow::Error> where FileSize: From, { let mut scoreboard = SizeOrderedScoreBoard::::with_capacity(cfg.number); let mut access_problem_seen = false; for startdir in &cfg.startdirs { for entry in WalkDir::new(startdir) .same_file_system(cfg.xdev) .into_iter() .filter_map(|r| r.map_err(|_| access_problem_seen = true).ok()) { if entry.path().is_dir() && !cfg.directories { debug!( "Skipping path' {}' because it is a directory", entry.path().display() ); continue; } match entry.metadata() { Ok(metadata) => { let filetime = get_time_attribute(cfg, &metadata)?; let to_add = SizeOrderedScoreBoardEntry:: { timeattr: filetime, path: entry.path().as_os_str().to_os_string().clone(), size_b: OrderingFileSize::from_filesize(metadata.len()), }; debug!("Adding to scoreboard: {to_add:?}"); scoreboard.push(to_add); if scoreboard.len() > cfg.number { let removed = scoreboard.pop(); debug!("Removed from scoreboard: {removed:?}"); } } Err(e) => { debug!( "Could not get metadata for path '{}': {e}", entry.path().display() ); } } } } if access_problem_seen { warn!("At least one inaccessible directory was skipped"); } Ok(scoreboard) } fn pathfilter(cfg: &Cfg, entry: &DirEntry) -> bool { if let Some(exclude_regex) = &cfg.exclude && exclude_regex.is_match(&entry.path().as_os_str().to_string_lossy()) { return false; } if let Some(include_regex) = &cfg.include && !include_regex.is_match(&entry.path().as_os_str().to_string_lossy()) { return false; } true } fn build_output_bytes(cfg: &Cfg, sb: OutputScoreBoard) -> Result> { if cfg.output_combo.contains(&OutputItem::Json) { let json_data = serde_json::to_vec(&sb)?; return Ok(json_data); } let mut out_bytes: Vec = Vec::new(); for sb_entry in sb { out_bytes.append(&mut get_output_record(cfg, sb_entry)); } Ok(out_bytes) } fn get_output_record(cfg: &Cfg, osb_entry: OutputScoreBoardEntry) -> Vec { if cfg.output_combo.contains(&OutputItem::Nulls) { let mut out_bytes = osb_entry.path.into_bytes(); out_bytes.push(b'\0'); return out_bytes; } let mut parts = Vec::new(); if cfg.output_combo.contains(&OutputItem::SizeBytes) { parts.push(osb_entry.size_b.to_string()); } if cfg.output_combo.contains(&OutputItem::TimeISO) { let dt: chrono::DateTime = osb_entry.timeattr.into(); parts.push(format!("{}", dt.format("%+"))); } if cfg.output_combo.contains(&OutputItem::TimeUnix) { let dur = osb_entry .timeattr .duration_since(SystemTime::UNIX_EPOCH) .unwrap_or_default() .as_secs(); parts.push(dur.to_string()); } if cfg.output_combo.contains(&OutputItem::Path) { // We don't want newlines in filenames in output parts.push(terminal_friendly_path(cfg, &osb_entry.path)); } let out_string = parts.join(" ") + "\n"; out_string.into_bytes() } // ================================================= // Utility functions // ================================================= fn sanity_check(cfg: &Cfg) -> Result<()> { if cfg.number == 0 && !cfg.weird { bail!("Number of returned files cannot be 0"); } if cfg.directories && !cfg.weird { if cfg.time_attribute == TimeAttribute::Accessed { bail!( "Sorting on access time and including directory-type directory entries is likely a mistake" ); } if (cfg.result_order == ResultOrder::Largest) || (cfg.result_order == ResultOrder::Smallest) { bail!( "Sorting on size and including directory-type directory entries is likely a mistake" ); } } if cfg.time_attribute != TimeAttribute::Unset && (cfg.result_order == ResultOrder::Largest || cfg.result_order == ResultOrder::Smallest) { bail!("Refusing to evaluate file time attributes when sorting on file size"); } if cfg.output_combo.output_items.is_empty() && !cfg.weird { bail!("It is likely a mistake to only want blank output lines"); } if cfg.output_combo.output_items.len() > 1 && cfg.output_combo.output_items.contains(&OutputItem::Nulls) { bail!("The 0 output option can not be combined with other output items"); } if !cfg.weird { let mut canonical_startpaths = Vec::new(); debug!("startdirs: {:?}", &cfg.startdirs); for startpath in &cfg.startdirs { let canonpath = canonicalize(startpath)?; debug!("canonpath seen: {}", canonpath.display()); canonical_startpaths.push(canonpath); } let unique: HashSet<_> = canonical_startpaths.iter().cloned().collect(); debug!("unique canonical start dirs: {unique:?}"); if unique.len() != canonical_startpaths.len() { bail!("The indicated start directories are not unique"); } } for startdir in &cfg.startdirs { if set_current_dir(startdir).is_err() { bail!( "Directory '{}' is inaccessible, or does not exist", startdir.to_string_lossy() ); } } Ok(()) } fn get_time_attribute(cfg: &Cfg, metadata: &Metadata) -> Result { let res = match cfg.time_attribute { TimeAttribute::Accessed => metadata .accessed() .context("Failed getting accessed time")?, TimeAttribute::Created => metadata.created().context("Failed getting created time")?, TimeAttribute::Modified => metadata .modified() .context("Failed getting modified time")?, TimeAttribute::Unset => bail!("Impossible TimeAttribute value(?!)"), }; Ok(res) } fn bailout(msg: &(impl Into + std::fmt::Display)) { eprintln!("Error: {msg}"); std::process::exit(1); } // This string sanetizer is used for output wich is neither // null-terminated ("print0") nor JSON-serialized. // In this situation we know that the path will be the last // element printed on each line. So we don't handle spaces in a // special way, because the path "record" will be easy to pick // out using -- for example -- "cut -d ' ' -f 2-", if need be. fn terminal_friendly_path(cfg: &Cfg, pathstr: &str) -> String { pathstr .chars() .flat_map(|c| { match c { // control characters '\x00'..='\x1F' => { if cfg.unicode_supported { // It's OK to unwrap() here, because the set of possible // input is rather limited, and it should be possible // to generate Unicode from any of the chars in \x00..\x1F: vec![char::from_u32(0x2400 + c as u32).unwrap()] } else { std::ascii::escape_default(c as u8) .map(char::from) .collect::>() } } '\x7f' => { // The odd DEL char if cfg.unicode_supported { vec!['\u{2421}'] } else { "^?".chars().collect() } } // All else left untouched _ => vec![c], } }) .collect() } fn parse_args() -> ArgMatches { let cmd = Command::new(env!("CARGO_CRATE_NAME")) .about(crate_description!().to_owned() + ".") .version(crate_version!()) .arg( Arg::new("attribute") .short('a') .long("attribute") .default_value("") .value_parser(clap::builder::PossibleValuesParser::new([ "", "accessed", "created", "modified", ])) .help("Which time attribute to sort on; default is 'modified'") ) .arg( Arg::new("directories") .short('d') .long("directories") .num_args(0) .help("Evaluate also directories(not only files)") ) .arg( Arg::new("number") .short('n') .long("number") .default_value(DEFAULT_NUMBER) .value_parser(clap::value_parser!(usize)) .help("Number of directory entries to return") ) .arg( Arg::new("exclude") .short('e') .long("exclude") .help("A regular expression which the path must NOT match in order for the file to be evaluated; the regular expression dialect is that of the Rust 'regex' crate") ) .arg( Arg::new("include") .short('i') .long("include") .help("A regular expression which the path must match in order for the file to be evaluated; the regular expression dialect is that of the Rust 'regex' crate") ) .arg( Arg::new("output") .short('o') .long("output") .default_value(DEFAULT_OUTPUT_COMBO) .help("What to output about each file: i=ISO time, j=json, p=human readable path, u=unixtime, b=size(bytes), 0=print0 '0' cannot be combined with other output elements; it means that output should be handled like the find utility does with its -print0 argument: Record separator is the null character instead of newline 'j' means JSON and cannot be combined with other output elements; resulting JSON will contain time, path, and size 'p' will replace problematic charaters (newlines, the bell char, etc) in paths with the Unicode control pictures or caret escaped charaters, depending on the capabilities of the environment" ) ) .arg( Arg::new("return") .short('r') .long("return") .default_value(DEFAULT_RETURN) .value_parser(clap::builder::PossibleValuesParser::new([ "largest", "oldest", "smallest", "youngest", ])) .help("What to sort on: oldest, youngest, largest, or smallest") ) .arg( Arg::new("weird") .short('w') .long("weird") .num_args(0) .help("Accept weird option combinations") ) .arg( Arg::new("xdev") .short('x') .long("xdev") .num_args(0) .help("Do not descend directories on other filesystems") ) .arg( Arg::new("startdirs") .required(true) .num_args(1..) .help("Which directory to start in; multiple start directories may be stated, separated by spaces") ); cmd.get_matches() } topnfiles-4.1.12/src/test.rs000064400000000000000000000315551046102023000140410ustar 00000000000000#![warn(clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated)] use super::*; use std::env::set_current_dir; use std::ffi::OsString; use std::fs::{File, create_dir, remove_dir_all}; use std::thread::sleep; use tempfile::{TempDir, tempdir}; fn setup_temporary_test_directory_structure1() -> (TempDir, Vec) { let tenms = std::time::Duration::from_millis(10); let mut tmp_dir = tempdir().unwrap(); tmp_dir.disable_cleanup(true); set_current_dir(&tmp_dir).unwrap(); let mut outvec = Vec::::new(); create_dir("topdir1").unwrap(); sleep(tenms); create_dir("topdir2").unwrap(); sleep(tenms); create_dir("topdir3").unwrap(); sleep(tenms); let file_path0 = tmp_dir.path().join("file0"); let _ = File::create(&file_path0).unwrap(); outvec.push(file_path0.as_os_str().to_os_string()); sleep(tenms); let file_path1 = tmp_dir.path().join("file1"); let mut fh = File::create(&file_path1).unwrap(); let line = "6chars"; let _ = write!(fh, "{}", line); outvec.push(file_path1.as_os_str().to_os_string()); sleep(tenms); let file_path2 = tmp_dir.path().join("file2"); let _ = File::create(&file_path2).unwrap(); outvec.push(file_path2.as_os_str().to_os_string()); sleep(tenms); let file_path3 = tmp_dir.path().join("file3"); let mut fh = File::create(&file_path3).unwrap(); let line = "15chars_xxxxxxx"; let _ = write!(fh, "{}", line); outvec.push(file_path3.as_os_str().to_os_string()); sleep(tenms); let file_path4 = tmp_dir.path().join("file4"); let _ = File::create(&file_path4).unwrap(); outvec.push(file_path4.as_os_str().to_os_string()); sleep(tenms); let file_path5 = tmp_dir.path().join("file5"); let mut fh = File::create(&file_path5).unwrap(); let line = "15chars_xxxxxxx"; let _ = write!(fh, "{}", line); outvec.push(file_path5.as_os_str().to_os_string()); sleep(tenms); let file_path6 = tmp_dir.path().join("file6"); let mut fh = File::create(&file_path6).unwrap(); let line = "1"; let _ = write!(fh, "{}", line); outvec.push(file_path6.as_os_str().to_os_string()); sleep(tenms); let file_path7 = tmp_dir.path().join("file7"); let mut fh = File::create(&file_path7).unwrap(); let line = "20chars_xxxxxxxxxxxx"; let _ = write!(fh, "{}", line); outvec.push(file_path7.as_os_str().to_os_string()); sleep(tenms); let file_path8 = tmp_dir.path().join("topdir1/file8"); let mut fh = File::create(&file_path8).unwrap(); let line = "22chars_xxxxxxxxxxxxxx"; let _ = write!(fh, "{}", line); outvec.push(file_path8.as_os_str().to_os_string()); sleep(tenms); let file_path9 = tmp_dir.path().join("topdir1/file9"); let mut fh = File::create(&file_path9).unwrap(); let line = "17chars_xxxxxxxxx"; let _ = write!(fh, "{}", line); outvec.push(file_path9.as_os_str().to_os_string()); sleep(tenms); let file_path10 = tmp_dir.path().join("topdir2/file10"); let _ = File::create(&file_path10).unwrap(); outvec.push(file_path10.as_os_str().to_os_string()); sleep(tenms); let file_path11 = tmp_dir.path().join("topdir1/file11"); let _ = File::create(&file_path11).unwrap(); outvec.push(file_path11.as_os_str().to_os_string()); sleep(tenms); let file_path12 = tmp_dir.path().join("file12"); let _ = File::create(&file_path12).unwrap(); outvec.push(file_path12.as_os_str().to_os_string()); sleep(tenms); let file_path13 = tmp_dir.path().join("file13"); let _ = File::create(&file_path13).unwrap(); outvec.push(file_path13.as_os_str().to_os_string()); sleep(tenms); let file_path14 = tmp_dir.path().join("file14"); let mut fh = File::create(&file_path14).unwrap(); let line = "333"; let _ = write!(fh, "{}", line); outvec.push(file_path14.as_os_str().to_os_string()); sleep(tenms); (tmp_dir, outvec) } fn setup_temporary_test_directory_structure2() -> (TempDir, Vec) { let mut tmp_dir = tempdir().unwrap(); tmp_dir.disable_cleanup(true); let mut outvec = Vec::::new(); let file_path_0_name = "This is del:\u{007f}, now a newline ending now."; let file_path0 = tmp_dir.path().join(file_path_0_name); let _ = File::create(&file_path0).unwrap(); outvec.push(file_path0.as_os_str().to_os_string()); (tmp_dir, outvec) } #[test] fn test_directory_structure1() { let mut do_cleanup = true; let (topdir, paths) = setup_temporary_test_directory_structure1(); // Test direction youngest // ========================================================= let mut expected_path_set_youngest = std::collections::HashSet::new(); expected_path_set_youngest.insert(paths[9].clone()); expected_path_set_youngest.insert(paths[10].clone()); expected_path_set_youngest.insert(paths[11].clone()); expected_path_set_youngest.insert(paths[12].clone()); expected_path_set_youngest.insert(paths[13].clone()); expected_path_set_youngest.insert(paths[14].clone()); let cfg = Cfg { directories: false, exclude: None, include: None, number: 6, output_combo: OutputCombo::default(), result_order: ResultOrder::Youngest, startdirs: vec![topdir.path().into()], time_attribute: TimeAttribute::Modified, unicode_supported: true, weird: false, xdev: false, }; let mut res = recurse_for_time::(&cfg).unwrap(); let mut resulting_path_set_youngest = std::collections::HashSet::new(); while let Some(entry) = res.pop() { resulting_path_set_youngest.insert(entry.path); } let set_diff = expected_path_set_youngest.symmetric_difference(&resulting_path_set_youngest); let potential_errmsg = format!( "resulting_path_set_youngest!=expected_path_set_youngest. Test directory {} will not be deleted", topdir.path().to_string_lossy() ); // We expect there to be no difference between expected and actual result set let went_ok = 0 == set_diff.collect::>().len(); if !went_ok { do_cleanup = false; } assert_eq!(went_ok, true, "{potential_errmsg}"); // Test direction oldest // ========================================================= let mut expected_path_set_oldest = std::collections::HashSet::new(); expected_path_set_oldest.insert(paths[5].clone()); expected_path_set_oldest.insert(paths[4].clone()); expected_path_set_oldest.insert(paths[3].clone()); expected_path_set_oldest.insert(paths[2].clone()); expected_path_set_oldest.insert(paths[1].clone()); expected_path_set_oldest.insert(paths[0].clone()); let cfg = Cfg { directories: false, exclude: None, include: None, number: 6, output_combo: OutputCombo::default(), result_order: ResultOrder::Oldest, startdirs: vec![topdir.path().into()], time_attribute: TimeAttribute::Modified, unicode_supported: true, weird: false, xdev: false, }; let mut res = recurse_for_time::(&cfg).unwrap(); let mut resulting_path_set_oldest = std::collections::HashSet::new(); while let Some(entry) = res.pop() { resulting_path_set_oldest.insert(entry.path); } let set_diff = expected_path_set_oldest.symmetric_difference(&resulting_path_set_oldest); let potential_errmsg = format!( "resulting_path_set_oldest!=expected_path_set_oldest. Test directory {} will not be deleted", topdir.path().to_string_lossy() ); // We expect there to be no difference between expected and actual result set let went_ok = 0 == set_diff.collect::>().len(); if !went_ok { do_cleanup = false; } assert_eq!(went_ok, true, "{potential_errmsg}"); // Test direction smallest // ========================================================= let mut expected_path_set_smallest = std::collections::HashSet::new(); expected_path_set_smallest.insert(paths[0].clone()); expected_path_set_smallest.insert(paths[2].clone()); expected_path_set_smallest.insert(paths[4].clone()); expected_path_set_smallest.insert(paths[10].clone()); expected_path_set_smallest.insert(paths[11].clone()); expected_path_set_smallest.insert(paths[12].clone()); expected_path_set_smallest.insert(paths[13].clone()); expected_path_set_smallest.insert(paths[6].clone()); expected_path_set_smallest.insert(paths[14].clone()); let cfg = Cfg { directories: false, exclude: None, include: None, number: 9, output_combo: OutputCombo::default(), result_order: ResultOrder::Smallest, startdirs: vec![topdir.path().into()], time_attribute: TimeAttribute::Modified, unicode_supported: true, weird: false, xdev: false, }; let mut res = recurse_for_size::(&cfg).unwrap(); let mut resulting_path_set_smallest = std::collections::HashSet::new(); while let Some(entry) = res.pop() { resulting_path_set_smallest.insert(entry.path); } let set_diff = expected_path_set_smallest.symmetric_difference(&resulting_path_set_smallest); let potential_errmsg = format!( "resulting_path_set!=expected_path_set_smallest. Test directory {} will not be deleted", topdir.path().to_string_lossy() ); // We expect there to be no difference between expected and actual result set let went_ok = 0 == set_diff.collect::>().len(); if !went_ok { do_cleanup = false; } assert_eq!(went_ok, true, "{potential_errmsg}"); // Test direction largest // ========================================================= let mut expected_path_set_largest = std::collections::HashSet::new(); expected_path_set_largest.insert(paths[9].clone()); expected_path_set_largest.insert(paths[3].clone()); expected_path_set_largest.insert(paths[5].clone()); expected_path_set_largest.insert(paths[8].clone()); expected_path_set_largest.insert(paths[7].clone()); let cfg = Cfg { directories: false, exclude: None, include: None, number: 5, output_combo: OutputCombo::default(), result_order: ResultOrder::Largest, startdirs: vec![topdir.path().into()], time_attribute: TimeAttribute::Modified, unicode_supported: true, weird: false, xdev: false, }; let mut res = recurse_for_size::(&cfg).unwrap(); let mut resulting_path_set_largest = std::collections::HashSet::new(); while let Some(entry) = res.pop() { resulting_path_set_largest.insert(entry.path); } let set_diff = expected_path_set_largest.symmetric_difference(&resulting_path_set_largest); let potential_errmsg = format!( "resulting_path_set!=expected_path_set_largest. Test directory {} will not be deleted", topdir.path().to_string_lossy() ); // We expect there to be no difference between expected and actual result set let went_ok = 0 == set_diff.collect::>().len(); if !went_ok { do_cleanup = false; } assert_eq!(went_ok, true, "{potential_errmsg}"); // Test cleanup // ========================================================= if !do_cleanup { let _ = remove_dir_all(topdir); } } #[test] fn test_special_chars_being_converted_to_unicode_control_pictures() { let (topdir, _) = setup_temporary_test_directory_structure2(); let output_combo = OutputCombo::from_str("p").unwrap(); let cfg = Cfg { directories: false, exclude: None, include: None, number: 1, output_combo, result_order: ResultOrder::Youngest, startdirs: vec![topdir.path().into()], time_attribute: TimeAttribute::Modified, unicode_supported: true, weird: false, xdev: false, }; let mut expected_bytes_after_topdir = vec![ 47, 84, 104, 105, 115, 32, 105, 115, 32, 100, 101, 108, 58, 226, 144, 161, 44, 32, 110, 111, 119, 32, 97, 32, 110, 101, 119, 108, 105, 110, 101, 226, 144, 138, 32, 101, 110, 100, 105, 110, 103, 32, 110, 111, 119, 46, 10, ]; let topdir_path_string = topdir.path().to_string_lossy().to_string(); let topdir_path_chars = topdir_path_string.chars(); let mut expected_bytes: Vec = topdir_path_chars.map(|c| c as u8).collect(); // to be built on expected_bytes.append(&mut expected_bytes_after_topdir); let osb = build_output_scoreboard(&cfg).unwrap(); let actual_bytes = build_output_bytes(&cfg, osb).unwrap(); let potential_errmsg = format!( "actual_bytes!=expected_bytes. Test directory {} will not be deleted", topdir.path().to_string_lossy() ); let went_ok = expected_bytes == actual_bytes; assert_eq!(went_ok, true, "{potential_errmsg}"); if went_ok { let _ = remove_dir_all(topdir); } } topnfiles-4.1.12/topnfiles.spec000064400000000000000000000025671046102023000146050ustar 00000000000000# Generated by rust2rpm 27 %bcond check 1 # prevent library files from being installed %global cargo_install_lib 0 Name: topnfiles Version: 4.1.12 Release: %autorelease Summary: Finds newest/oldest/smallest/largest files in a directory structure SourceLicense: MIT License: %{shrink: (MIT OR Apache-2.0) AND Unicode-DFS-2016 Apache-2.0 Apache-2.0 OR BSL-1.0 Apache-2.0 OR MIT Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT MIT MIT OR Apache-2.0 Unlicense OR MIT } URL: https://gitlab.com/troelsarvin/topnfiles Source: %{url}/-/archive/%{version}/%{name}-%{version}.tar.gz BuildRequires: cargo-rpm-macros >= 26 %global _description %{expand: Recursively finds n most/least recently modified or smallest/largest files in a directory structure.} %description %{_description} %prep %autosetup -n topnfiles-%{version} -p1 %cargo_prep %generate_buildrequires %cargo_generate_buildrequires %build %cargo_build %{cargo_license_summary} %{cargo_license} > LICENSE.dependencies %install %cargo_install install -Dpm 0644 packaging%{_mandir}/man1/%{name}.1 %{buildroot}%{_mandir}/man1/%{name}.1 %if %{with check} %check %cargo_test %endif %files %license LICENSE.txt %license LICENSE.dependencies %doc CHANGELOG.md %doc README.md %{_bindir}/topnfiles %{_mandir}/man1/%{name}.1* %changelog %autochangelog