dysk-cli-3.6.0/.cargo_vcs_info.json0000644000000001410000000000100125550ustar { "git": { "sha1": "6eaac2da0c38e7955445098f685c10cf1b3eccb2" }, "path_in_vcs": "cli" }dysk-cli-3.6.0/Cargo.lock0000644000000631630000000000100105450ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[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", ] [[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", ] [[package]] name = "bet" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a36860e45f881828e54f9e6438516185c64fe63da4a2c26fbf60f326f40c9005" [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap-help" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d86ee73ada209cd33df09d6fdff344796ffd941bf960fcdcc611847cb6985b" dependencies = [ "clap", "termimad", "terminal-light", ] [[package]] name = "clap_builder" version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.111", ] [[package]] name = "clap_lex" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "convert_case" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] [[package]] name = "coolor" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3" dependencies = [ "crossterm", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crokey" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51360853ebbeb3df20c76c82aecf43d387a62860f1a59ba65ab51f00eea85aad" dependencies = [ "crokey-proc_macros", "crossterm", "once_cell", "serde", "strict", ] [[package]] name = "crokey-proc_macros" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bf1a727caeb5ee5e0a0826a97f205a9cf84ee964b0b48239fef5214a00ae439" dependencies = [ "crossterm", "proc-macro2", "quote", "strict", "syn 2.0.111", ] [[package]] name = "crossbeam" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", "crossbeam-queue", "crossbeam-utils", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot", "rustix", "signal-hook", "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] [[package]] name = "derive_more" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", "syn 2.0.111", ] [[package]] name = "doc-comment" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" [[package]] name = "document-features" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] [[package]] name = "dysk-cli" version = "3.6.0" dependencies = [ "bet", "clap", "clap-help", "file-size", "lfs-core", "serde", "serde_json", "termimad", ] [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys", ] [[package]] name = "file-size" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9544f10105d33957765016b8a9baea7e689bf1f0f2f32c2fa2f568770c38d2b3" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "io-kit-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" dependencies = [ "core-foundation-sys", "mach2", ] [[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.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "lazy-regex" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "191898e17ddee19e60bccb3945aa02339e81edd4a8c50e21fd4d48cdecda7b29" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c35dc8b0da83d1a9507e12122c80dea71a9c7c613014347392483a83ea593e04" dependencies = [ "proc-macro2", "quote", "regex", "syn 2.0.111", ] [[package]] name = "lfs-core" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a82bb0d309ee7cb41f0f87843945b1a03c48d13cfb4069a18bcc8d34e50ac5" dependencies = [ "core-foundation", "core-foundation-sys", "io-kit-sys", "lazy-regex", "libc", "snafu", "windows", ] [[package]] name = "libc" version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "linux-raw-sys" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litrs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mach2" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "minimad" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8b688969b16915f3ecadc7829d5b7779dee4977e503f767f34136803d5c06f" dependencies = [ "once_cell", ] [[package]] name = "mio" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", "wasi", "windows-sys", ] [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", ] [[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 = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[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]] name = "regex-automata" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[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", ] [[package]] name = "ryu" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[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 2.0.111", ] [[package]] name = "serde_json" version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-mio" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "snafu" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" dependencies = [ "doc-comment", "snafu-derive", ] [[package]] name = "snafu-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "strict" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f42444fea5b87a39db4218d9422087e66a85d0e7a0963a439b07bcdf91804006" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termimad" version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "889a9370996b74cf46016ce35b96c248a9ac36d69aab1d112b3e09bc33affa49" dependencies = [ "coolor", "crokey", "crossbeam", "lazy-regex", "minimad", "serde", "thiserror 2.0.17", "unicode-width", ] [[package]] name = "terminal-light" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6f76be906d875a0ce764c52a055858c24847cb7dc674d3a5ad8cf7e3dd4ee9f" dependencies = [ "coolor", "crossterm", "thiserror 1.0.69", "xterm-query", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.111", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn 2.0.111", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", "windows-core", "windows-future", "windows-numerics", ] [[package]] name = "windows-collections" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ "windows-core", ] [[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-future" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core", "windows-link", "windows-threading", ] [[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.111", ] [[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.111", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core", "windows-link", ] [[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.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ "windows-link", ] [[package]] name = "xterm-query" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "292c33df434fde4ecd87a7afecdfa1681a3d29567fc69c774a0d83d32c095331" dependencies = [ "nix", "thiserror 1.0.69", ] dysk-cli-3.6.0/Cargo.toml0000644000000024010000000000100105540ustar # 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 = "2021" rust-version = "1.70" name = "dysk-cli" version = "3.6.0" authors = ["dystroy "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "the dysk cli as a library" homepage = "https://dystroy.org/dysk" readme = false license = "MIT" resolver = "1" [lib] name = "dysk_cli" path = "src/lib.rs" [dependencies.bet] version = "1.0.4" [dependencies.clap] version = "4.4" features = [ "derive", "cargo", ] [dependencies.clap-help] version = "1.4" [dependencies.file-size] version = "1.0.3" [dependencies.lfs-core] version = "0.19.2" [dependencies.serde] version = "1.0" [dependencies.serde_json] version = "1.0" [dependencies.termimad] version = "0.34.1" [profile.release] strip = true dysk-cli-3.6.0/Cargo.toml.orig000064400000000000000000000011731046102023000142420ustar 00000000000000[package] name = "dysk-cli" version = "3.6.0" authors = ["dystroy "] edition = "2021" license = "MIT" description = "the dysk cli as a library" homepage = "https://dystroy.org/dysk" rust-version = "1.70" resolver = "1" [dependencies] bet = "1.0.4" clap = { version = "4.4", features = ["derive", "cargo"] } clap-help = "1.4" file-size = "1.0.3" #lfs-core = { git = "https://github.com/Canop/lfs-core.git", branch = "smb" } #lfs-core = { path = "../../lfs-core" } lfs-core = "0.19.2" serde = "1.0" serde_json = "1.0" termimad = "0.34.1" #termimad = { path = "../../termimad" } [profile.release] strip = true dysk-cli-3.6.0/src/args.rs000064400000000000000000000057341046102023000134530ustar 00000000000000use { crate::{ cols::Cols, filter::Filter, sorting::Sorting, timeout::Timeout, units::Units, }, clap::{ Parser, ValueEnum, }, termimad::crossterm::tty::IsTty, }; /// List your filesystems. /// /// Documentation at https://dystroy.org/dysk #[derive(Debug, Parser)] #[command( author, about, name = "dysk", disable_version_flag = true, version, disable_help_flag = true )] pub struct Args { /// print help information #[arg(long)] pub help: bool, /// print the version #[arg(long)] pub version: bool, /// show all mount points #[arg(short, long)] pub all: bool, /// whether to have styles and colors #[arg(long, default_value = "auto", value_name = "color")] pub color: TriBool, /// use only ASCII characters for table rendering #[arg(long)] pub ascii: bool, /// fetch stats of remote volumes #[arg(long, default_value = "auto", value_name = "choice")] pub remote_stats: TriBool, /// list the column names which can be used in -s, -f, or -c #[arg(long)] pub list_cols: bool, /// columns, eg `-c +inodes` or `-c id+dev+default` #[arg( short, long, default_value = "fs+type+disk+used+use+free+size+mp", value_name = "columns" )] pub cols: Cols, /// filter, eg `-f '(size<35G | remote=false) & type=xfs'` #[arg(short, long, value_name = "expr")] pub filter: Option, /// sort, eg `inodes`, `type-desc`, or `size-asc` #[arg(short, long, default_value = "size", value_name = "sort")] pub sort: Sorting, /// units: `SI` (SI norm), `binary` (1024 based), or `bytes` (raw number) #[arg(short, long, default_value = "SI", value_name = "unit")] pub units: Units, /// output as JSON #[arg(short, long)] pub json: bool, /// output as CSV #[arg(long)] pub csv: bool, /// CSV separator #[arg(long, default_value = ",", value_name = "sep")] pub csv_separator: char, /// strategy to use to find information, when several are available #[arg(long, hide = true)] pub strategy: Option, /// timeout to use for some operations, eg `10s` or `5ms` #[arg(long)] pub timeout: Option, /// if provided, only the device holding this path will be shown pub path: Option, } /// This is an Option but I didn't find any way to configure /// clap to parse an Option as I want #[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)] pub enum TriBool { Auto, Yes, No, } impl TriBool { pub fn unwrap_or_else( self, f: F, ) -> bool where F: FnOnce() -> bool, { match self { Self::Auto => f(), Self::Yes => true, Self::No => false, } } } impl Args { pub fn color(&self) -> bool { self.color.unwrap_or_else(|| std::io::stdout().is_tty()) } } dysk-cli-3.6.0/src/col.rs000064400000000000000000000324111046102023000132640ustar 00000000000000use { crate::order::Order, lfs_core::Mount, std::{ cmp::Ordering, fmt, str::FromStr, }, termimad::minimad::Alignment, }; macro_rules! col_enum { (@just_variant $variant:ident $discarded:ident) => { Col::$variant }; ($($variant:ident $name:literal $($alias:literal)* : $title:literal $($def:ident)*,)*) => { /// A column of the lfs table. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Col { $($variant,)* } pub static ALL_COLS: &[Col] = &[ $(Col::$variant,)* ]; pub static DEFAULT_COLS: &[Col] = &[ $( $(col_enum!(@just_variant $variant $def),)* )* ]; impl FromStr for Col { type Err = ParseColError; fn from_str(s: &str) -> Result { match s { $( $name => Ok(Self::$variant), $( $alias => Ok(Self::$variant), )* )* _ => Err(ParseColError::new(s)), } } } impl fmt::Display for Col { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { $( Self::$variant => write!(f, "{}", self.title()), )* } } } impl Col { pub fn name(self) -> &'static str { match self { $( Self::$variant => $name, )* } } pub fn title(self) -> &'static str { match self { $( Self::$variant => $title, )* } } pub fn aliases(self) -> &'static [&'static str] { match self { $( Self::$variant => &[$($alias,)*], )* } } pub fn is_default(self) -> bool { DEFAULT_COLS.contains(&self) } } }; } // definition of all columns and their names // in the --cols definition col_enum!( // syntax: // Variant name [aliases]: title [default] Id "id": "id", Dev "dev" "device" "device_id": "dev", Filesystem "fs" "filesystem": "filesystem" default, Label "label": "label", Type "type": "type" default, Remote "remote" "rem": "remote", Disk "disk" "dsk": "disk" default, Used "used": "used" default, Use "use": "use" default, UsePercent "use_percent": "use%", Free "free": "free" default, FreePercent "free_percent": "free%", Size "size": "size" default, InodesUsed "inodes_used" "iused": "used inodes", InodesUse "inodes" "ino" "inodes_use" "iuse": "inodes", InodesUsePercent "inodes_use_percent" "iuse_percent": "inodes%", InodesFree "inodes_free" "ifree": "free inodes", InodesCount "inodes_total" "inodes_count" "itotal": "inodes total", MountPoint "mount" "mount_point" "mp": "mount point" default, Uuid "uuid": "UUID", PartUuid "partuuid" "part_uuid": "PARTUUID", MountOptions "options" "mount_options": "mount options", CompressLevel "compress" "compress_level": "compress", ); impl Col { pub fn header_align(self) -> Alignment { match self { Self::Label => Alignment::Left, Self::MountPoint => Alignment::Left, Self::MountOptions => Alignment::Left, _ => Alignment::Center, } } pub fn content_align(self) -> Alignment { match self { Self::Id => Alignment::Right, Self::Dev => Alignment::Center, Self::Filesystem => Alignment::Left, Self::Label => Alignment::Left, Self::Type => Alignment::Center, Self::Remote => Alignment::Center, Self::Disk => Alignment::Center, Self::Used => Alignment::Right, Self::Use => Alignment::Right, Self::UsePercent => Alignment::Right, Self::Free => Alignment::Right, Self::FreePercent => Alignment::Right, Self::Size => Alignment::Right, Self::InodesUsed => Alignment::Right, Self::InodesUse => Alignment::Right, Self::InodesUsePercent => Alignment::Right, Self::InodesFree => Alignment::Right, Self::InodesCount => Alignment::Right, Self::MountPoint => Alignment::Left, Self::Uuid => Alignment::Left, Self::PartUuid => Alignment::Left, Self::MountOptions => Alignment::Left, Self::CompressLevel => Alignment::Center, } } pub fn description(self) -> &'static str { match self { Self::Id => "mount point id", Self::Dev => "device id", Self::Filesystem => "filesystem", Self::Label => "volume label", Self::Type => "filesystem type", Self::Remote => "whether it's a remote filesystem", Self::Disk => "storage type", Self::Used => "size used", Self::Use => "usage graphical view", Self::UsePercent => "percentage of blocks used", Self::Free => "free bytes", Self::FreePercent => "percentage of free blocks", Self::Size => "total size", Self::InodesUsed => "number of inodes used", Self::InodesUse => "graphical view of inodes usage", Self::InodesUsePercent => "percentage of inodes used", Self::InodesFree => "number of free inodes", Self::InodesCount => "total count of inodes", Self::MountPoint => "mount point", Self::Uuid => "filesystem UUID", Self::PartUuid => "partition UUID", Self::MountOptions => "mount options (linux only)", Self::CompressLevel => "compress algo/level", } } pub fn comparator(self) -> impl for<'a, 'b> FnMut(&'a Mount, &'b Mount) -> Ordering { match self { Self::Id => |a: &Mount, b: &Mount| a.info.id.cmp(&b.info.id), Self::Dev => |a: &Mount, b: &Mount| a.info.dev.cmp(&b.info.dev), Self::Filesystem => |a: &Mount, b: &Mount| a.info.fs.cmp(&b.info.fs), Self::Label => |a: &Mount, b: &Mount| match (&a.fs_label, &b.fs_label) { (Some(a), Some(b)) => a.cmp(b), (Some(_), None) => Ordering::Less, (None, Some(_)) => Ordering::Greater, (None, None) => Ordering::Equal, }, Self::Type => |a: &Mount, b: &Mount| a.info.fs_type.cmp(&b.info.fs_type), Self::Remote => |a: &Mount, b: &Mount| a.is_remote().cmp(&b.is_remote()), Self::Disk => |a: &Mount, b: &Mount| match (&a.disk, &b.disk) { (Some(a), Some(b)) => a .disk_type() .to_lowercase() .cmp(&b.disk_type().to_lowercase()), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::Used => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) { (Some(a), Some(b)) => a.used().cmp(&b.used()), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::Use | Self::UsePercent => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) { // the 'use' column shows the percentage of used blocks, so it makes sense // to sort by use_share for it // SAFETY: use_share() doesn't return NaN (Some(a), Some(b)) => a.use_share().partial_cmp(&b.use_share()).unwrap(), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::Free => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) { (Some(a), Some(b)) => a.available().cmp(&b.available()), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::FreePercent => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) { (Some(a), Some(b)) => b.use_share().partial_cmp(&a.use_share()).unwrap(), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::Size => |a: &Mount, b: &Mount| match (&a.stats(), &b.stats()) { (Some(a), Some(b)) => a.size().cmp(&b.size()), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::InodesUsed => |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) { (Some(a), Some(b)) => a.used().cmp(&b.used()), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::InodesUsePercent | Self::InodesUse => { |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) { // SAFETY: use_share() doesn't return NaN (Some(a), Some(b)) => a.use_share().partial_cmp(&b.use_share()).unwrap(), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, } } Self::InodesFree => |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) { (Some(a), Some(b)) => a.favail.cmp(&b.favail), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::InodesCount => |a: &Mount, b: &Mount| match (&a.inodes(), &b.inodes()) { (Some(a), Some(b)) => a.files.cmp(&b.files), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, }, Self::MountPoint => |a: &Mount, b: &Mount| a.info.mount_point.cmp(&b.info.mount_point), Self::Uuid => |a: &Mount, b: &Mount| match (&a.uuid, &b.uuid) { (Some(a), Some(b)) => a.cmp(b), (Some(_), None) => Ordering::Less, (None, Some(_)) => Ordering::Greater, (None, None) => Ordering::Equal, }, Self::PartUuid => |a: &Mount, b: &Mount| match (&a.part_uuid, &b.part_uuid) { (Some(a), Some(b)) => a.cmp(b), (Some(_), None) => Ordering::Less, (None, Some(_)) => Ordering::Greater, (None, None) => Ordering::Equal, }, Self::MountOptions => |a: &Mount, b: &Mount| a.info.options_string() .cmp(&b.info.options_string()), Self::CompressLevel => |a: &Mount, b: &Mount| match (a.info.option_value("compress"), b.info.option_value("compress")) { (Some(a), Some(b)) => a.cmp(b), (Some(_), None) => Ordering::Less, (None, Some(_)) => Ordering::Greater, (None, None) => Ordering::Equal, }, } } pub fn default_sort_order(self) -> Order { match self { Self::Id => Order::Asc, Self::Dev => Order::Asc, Self::Filesystem => Order::Asc, Self::Label => Order::Asc, Self::Type => Order::Asc, Self::Remote => Order::Desc, Self::Disk => Order::Asc, Self::Used => Order::Asc, Self::Use => Order::Desc, Self::UsePercent => Order::Asc, Self::Free => Order::Asc, Self::FreePercent => Order::Desc, Self::Size => Order::Desc, Self::InodesUsed => Order::Asc, Self::InodesUse => Order::Asc, Self::InodesUsePercent => Order::Asc, Self::InodesFree => Order::Asc, Self::InodesCount => Order::Asc, Self::MountPoint => Order::Asc, Self::Uuid => Order::Asc, Self::PartUuid => Order::Asc, Self::MountOptions => Order::Asc, Self::CompressLevel => Order::Asc, } } pub fn default_sort_col() -> Self { Self::Size } } #[derive(Debug)] pub struct ParseColError { /// the string which couldn't be parsed pub raw: String, } impl ParseColError { pub fn new>(s: S) -> Self { Self { raw: s.into() } } } impl fmt::Display for ParseColError { fn fmt( &self, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { write!( f, "{:?} can't be parsed as a column; use 'dysk --list-cols' to see all column names", self.raw, ) } } impl std::error::Error for ParseColError {} dysk-cli-3.6.0/src/col_expr.rs000064400000000000000000000256131046102023000143300ustar 00000000000000use { crate::col::*, lfs_core::*, std::{ fmt, str::FromStr, }, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ColOperator { Lower, LowerOrEqual, Like, Equal, NotEqual, GreaterOrEqual, Greater, } impl ColOperator { pub fn eval( self, a: T, b: T, ) -> bool { match self { Self::Lower => a < b, Self::LowerOrEqual => a <= b, Self::Equal | Self::Like => a == b, Self::NotEqual => a != b, Self::GreaterOrEqual => a >= b, Self::Greater => a > b, } } pub fn eval_option( self, a: Option, b: T, ) -> bool { match a { Some(a) => self.eval(a, b), None => false, } } pub fn eval_str( self, a: &str, b: &str, ) -> bool { match self { Self::Like => a.to_lowercase().contains(&b.to_lowercase()), _ => self.eval(a, b), } } pub fn eval_option_str( self, a: Option<&str>, b: &str, ) -> bool { match (a, self) { (Some(a), Self::Like) => a.to_lowercase().contains(&b.to_lowercase()), _ => self.eval_option(a, b), } } } /// A leaf in the filter expression tree, an expression which /// may return true or false for any filesystem #[derive(Debug, Clone, PartialEq)] pub struct ColExpr { col: Col, operator: ColOperator, value: String, } impl ColExpr { #[cfg(test)] pub fn new>( col: Col, operator: ColOperator, value: S, ) -> Self { Self { col, operator, value: value.into(), } } pub fn eval( &self, mount: &Mount, ) -> Result { Ok(match self.col { Col::Id => self.operator.eval_option( mount.info.id, self.value .parse::() .map_err(|_| EvalExprError::NotAnId(self.value.to_string()))?, ), Col::Dev => self.operator.eval( mount.info.dev, self.value .parse::() .map_err(|_| EvalExprError::NotADeviceId(self.value.to_string()))?, ), Col::Filesystem => self.operator.eval_str(&mount.info.fs, &self.value), Col::Label => self .operator .eval_option_str(mount.fs_label.as_deref(), &self.value), Col::Type => self.operator.eval_str(&mount.info.fs_type, &self.value), Col::Remote => self .operator .eval(mount.is_remote(), parse_bool(&self.value)?), Col::Disk => self .operator .eval_option_str(mount.disk.as_ref().map(|d| d.disk_type()), &self.value), Col::Used => self.operator.eval_option( mount.stats().as_ref().map(|s| s.used()), parse_integer(&self.value)?, ), Col::Use | Col::UsePercent => self.operator.eval_option( mount.stats().as_ref().map(|s| s.use_share()), parse_float(&self.value)?, ), Col::Free | Col::FreePercent => self.operator.eval_option( mount.stats().as_ref().map(|s| s.available()), parse_integer(&self.value)?, ), Col::Size => self.operator.eval_option( mount.stats().as_ref().map(|s| s.size()), parse_integer(&self.value)?, ), Col::InodesUsed => self.operator.eval_option( mount.inodes().as_ref().map(|i| i.used()), parse_integer(&self.value)?, ), Col::InodesUse | Col::InodesUsePercent => self.operator.eval_option( mount.inodes().as_ref().map(|i| i.use_share()), parse_float(&self.value)?, ), Col::InodesFree => self.operator.eval_option( mount.inodes().as_ref().map(|i| i.favail), parse_integer(&self.value)?, ), Col::InodesCount => self.operator.eval_option( mount.inodes().as_ref().map(|i| i.files), parse_integer(&self.value)?, ), Col::MountPoint => self .operator .eval_str(&mount.info.mount_point.to_string_lossy(), &self.value), Col::Uuid => self .operator .eval_option_str(mount.uuid.as_deref(), &self.value), Col::PartUuid => self .operator .eval_option_str(mount.part_uuid.as_deref(), &self.value), Col::MountOptions => self.operator.eval_str(&mount.info.options_string(), &self.value), Col::CompressLevel => self .operator .eval_option_str(mount.info.option_value("compress"), &self.value), }) } } #[derive(Debug)] pub struct ParseExprError { /// the string which couldn't be parsed pub raw: String, /// why pub message: String, } impl ParseExprError { pub fn new, M: Into>( raw: R, message: M, ) -> Self { Self { raw: raw.into(), message: message.into(), } } } impl fmt::Display for ParseExprError { fn fmt( &self, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { write!( f, "{:?} can't be parsed as an expression: {}", self.raw, self.message ) } } impl std::error::Error for ParseExprError {} impl FromStr for ColExpr { type Err = ParseExprError; fn from_str(input: &str) -> Result { let mut chars_indices = input.char_indices(); let mut op_idx = 0; for (idx, c) in &mut chars_indices { if c == '<' || c == '>' || c == '=' { op_idx = idx; break; } } if op_idx == 0 { return Err(ParseExprError::new( input, "Invalid expression; expected ", )); } let mut val_idx = op_idx + 1; for (idx, c) in &mut chars_indices { if c != '<' && c != '>' && c != '=' { val_idx = idx; break; } } if val_idx == input.len() { return Err(ParseExprError::new(input, "no value")); } let col = &input[..op_idx]; let col = col .parse() .map_err(|e: ParseColError| ParseExprError::new(input, e.to_string()))?; let operator = match &input[op_idx..val_idx] { "<" => ColOperator::Lower, "<=" => ColOperator::LowerOrEqual, "=" => ColOperator::Like, "==" => ColOperator::Equal, "<>" => ColOperator::NotEqual, ">=" => ColOperator::GreaterOrEqual, ">" => ColOperator::Greater, op => { return Err(ParseExprError::new( input, format!("unknown operator: {:?}", op), )); } }; let value = &input[val_idx..]; let value = value.into(); Ok(Self { col, operator, value, }) } } #[test] fn test_col_filter_parsing() { assert_eq!( "remote=false".parse::().unwrap(), ColExpr::new(Col::Remote, ColOperator::Like, "false"), ); assert_eq!( "size<32G".parse::().unwrap(), ColExpr::new(Col::Size, ColOperator::Lower, "32G"), ); } #[derive(Debug, PartialEq)] #[allow(clippy::enum_variant_names)] pub enum EvalExprError { NotANumber(String), NotAnId(String), NotADeviceId(String), NotABool(String), } impl EvalExprError {} impl fmt::Display for EvalExprError { fn fmt( &self, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match self { Self::NotANumber(s) => { write!(f, "{:?} can't be evaluated as a number", &s) } Self::NotAnId(s) => { write!(f, "{:?} can't be evaluated as an id", &s) } Self::NotADeviceId(s) => { write!(f, "{:?} can't be evaluated as a device id", &s) } Self::NotABool(s) => { write!(f, "{:?} can't be evaluated as a boolean", &s) } } } } impl std::error::Error for EvalExprError {} fn parse_bool(input: &str) -> Result { let s = input.to_lowercase(); match s.as_ref() { "x" | "t" | "true" | "1" | "y" | "yes" => Ok(true), "f" | "false" | "0" | "n" | "no" => Ok(false), _ => Err(EvalExprError::NotABool(input.to_string())), } } /// Parse numbers like "1234", "32G", "4kB", "54Gib", "1.2M" fn parse_integer(input: &str) -> Result { let s = input.to_lowercase(); let s = s.trim_end_matches('b'); let (s, binary) = match s.strip_suffix('i') { Some(s) => (s, true), None => (s, false), }; let cut = s.find(|c: char| !(c.is_ascii_digit() || c == '.')); let (digits, factor): (&str, u64) = match cut { Some(idx) => ( &s[..idx], match (&s[idx..], binary) { ("k", false) => 1000, ("k", true) => 1024, ("m", false) => 1000 * 1000, ("m", true) => 1024 * 1024, ("g", false) => 1000 * 1000 * 1000, ("g", true) => 1024 * 1024 * 1024, ("t", false) => 1000 * 1000 * 1000 * 1000, ("t", true) => 1024 * 1024 * 1024 * 1024, _ => { // it's not a number return Err(EvalExprError::NotANumber(input.to_string())); } }, ), None => (s, 1), }; match digits.parse::() { Ok(n) => Ok((n * factor as f64).ceil() as u64), _ => Err(EvalExprError::NotANumber(input.to_string())), } } #[test] fn test_parse_integer() { assert_eq!(parse_integer("33"), Ok(33)); assert_eq!(parse_integer("55G"), Ok(55_000_000_000)); assert_eq!(parse_integer("1.23kiB"), Ok(1260)); } /// parse numbers like "0.25", "50%" fn parse_float(input: &str) -> Result { let s = input.to_lowercase(); let (s, percent) = match s.strip_suffix('%') { Some(s) => (s, true), None => (s.as_str(), false), }; let mut n = s .parse::() .map_err(|_| EvalExprError::NotANumber(input.to_string()))?; if percent { n /= 100.0; } Ok(n) } #[test] fn test_parse_float() { assert_eq!(parse_float("50%").unwrap().to_string(), "0.5".to_string()); } dysk-cli-3.6.0/src/cols.rs000064400000000000000000000253331046102023000134540ustar 00000000000000use { crate::col::*, std::str::FromStr, }; /// Sequence of columns, ordered #[derive(Debug, Clone, PartialEq)] pub struct Cols(pub Vec); impl Default for Cols { fn default() -> Self { Self(DEFAULT_COLS.to_vec()) } } impl Cols { #[cfg(test)] pub fn new>>(v: V) -> Self { Self(v.into()) } pub fn empty() -> Self { Self(Vec::new()) } pub fn is_empty(&self) -> bool { self.0.is_empty() } pub fn contains( &self, tbl: Col, ) -> bool { self.0.contains(&tbl) } pub fn remove( &mut self, removed: Col, ) { self.0.retain(|&f| f != removed); } /// Add a col, preventing duplicates /// (may be used when the col is present to reorder) pub fn add( &mut self, added: Col, ) { self.remove(added); self.0.push(added); } /// Add the columns of the set, except when they're /// already present /// /// This makes it possible to add a set while keeping /// the order of the previous columns, for example /// `dysk -c disk+` pub fn add_set( &mut self, col_set: &[Col], ) { if self.0 == ALL_COLS { for &col in col_set { self.add(col); } } else { for &col in col_set { if !self.contains(col) { self.add(col); } } } } pub fn remove_set( &mut self, col_set: &[Col], ) { for &col in col_set { self.remove(col); } } pub fn cols(&self) -> &[Col] { &self.0 } } impl FromStr for Cols { type Err = ParseColError; fn from_str(value: &str) -> Result { let value = value.trim(); let mut tokens: Vec = Vec::new(); let mut must_create = true; for c in value.chars() { if c.is_alphabetic() || c == '_' { if must_create { tokens.push(c.into()); must_create = false; } else { let len = tokens.len(); tokens[len - 1].push(c); } } else { tokens.push(c.into()); must_create = true; } } let mut cols = if let Some(first_token) = tokens.first() { if first_token == "+" || first_token == "-" { // if it starts with an addition or removal, the // default set is implied Cols::default() } else { Cols::empty() } } else { return Ok(Self::default()); }; let mut negative = false; for token in &tokens { match token.as_ref() { "-" => { negative = true; } "+" | "," | " " => {} "all" => { if negative { cols = Cols::empty(); negative = false; } else { // if we add all to something, it means the already // present one are meant to be first for &col in ALL_COLS { if !cols.contains(col) { cols.add(col); } } } } "default" => { if negative { cols.remove_set(DEFAULT_COLS); negative = false; } else { cols.add_set(DEFAULT_COLS); } } _ => { let col: Col = token.parse()?; if negative { cols.remove(col); negative = false; } else { cols.add(col); } } } } match tokens.last().map(|s| s.as_ref()) { Some("-") => { cols.remove_set(DEFAULT_COLS); } Some("+") => { cols.add_set(DEFAULT_COLS); } _ => {} } Ok(cols) } } #[cfg(test)] mod cols_parsing { use super::{ Col::*, *, }; fn check>>( s: &str, v: V, ) { println!("cols definition: {s:?}"); let from_str: Cols = s.parse().unwrap(); let from_vec: Cols = Cols::new(v); assert_eq!(from_str, from_vec); } #[test] fn bad_cols() { assert_eq!( "nothing".parse::().unwrap_err().to_string(), r#""nothing" can't be parsed as a column; use 'dysk --list-cols' to see all column names"#, ); } #[test] fn explicit_cols() { check("dev", vec![Dev]); check("dev,free,used", vec![Dev, Free, Used]); check("dev+free + used", vec![Dev, Free, Used]); check(" dev free used ", vec![Dev, Free, Used]); check("all", ALL_COLS); } #[test] fn algebraic_cols() { check( "all - dev -inodes + label", vec![ Id, Filesystem, Type, Remote, Disk, Used, Use, UsePercent, Free, FreePercent, Size, InodesUsed, InodesUsePercent, InodesFree, InodesCount, MountPoint, Uuid, PartUuid, Label, ], ); check("dev + dev +disk - use + size", vec![Dev, Disk, Size]); check( "all-default+use", vec![ Id, Dev, Label, Remote, UsePercent, FreePercent, InodesUsed, InodesUse, InodesUsePercent, InodesFree, InodesCount, Uuid, PartUuid, Use, ], ); check( "all+default", // special: all but default at the end vec![ Id, Dev, Label, Remote, UsePercent, FreePercent, InodesUsed, InodesUse, InodesUsePercent, InodesFree, InodesCount, Uuid, PartUuid, Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, ], ); check( "fs dev all", // we want all column but fs and dev at the start vec![ Filesystem, Dev, Id, Label, Type, Remote, Disk, Used, Use, UsePercent, Free, FreePercent, Size, InodesUsed, InodesUse, InodesUsePercent, InodesFree, InodesCount, MountPoint, Uuid, PartUuid, ], ); check( "fs dev all -id-disk", vec![ Filesystem, Dev, Label, Type, Remote, Used, Use, UsePercent, Free, FreePercent, Size, InodesUsed, InodesUse, InodesUsePercent, InodesFree, InodesCount, MountPoint, Uuid, PartUuid, ], ); } #[test] fn cols_from_default() { check("", DEFAULT_COLS); check( "-dev", // no impact as dev isn't in defaults DEFAULT_COLS, ); check("default", DEFAULT_COLS); check( "-default", // not really useful vec![], ); check( "default-dev", // no impact as dev isn't in defaults DEFAULT_COLS, ); check( "+dev", vec![ Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, Dev, ], ); check( "dev+", vec![ Dev, Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, ], ); check( "all-", vec![ Id, Dev, Label, Remote, UsePercent, FreePercent, InodesUsed, InodesUse, InodesUsePercent, InodesFree, InodesCount, Uuid, PartUuid, ], ); check( "-size+inodes_free+", vec![ Filesystem, Type, Disk, Used, Use, Free, MountPoint, InodesFree, Size, ], ); check( "+dev-size+inodes_use", vec![ Filesystem, Type, Disk, Used, Use, Free, MountPoint, Dev, InodesUse, ], ); check( "-use-type", vec![Filesystem, Disk, Used, Free, Size, MountPoint], ); check( "default+dev", vec![ Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, Dev, ], ); check( "default,size+use", // just reordering vec![Filesystem, Type, Disk, Used, Free, MountPoint, Size, Use], ); check( "dev default", vec![ Dev, Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, ], ); check( "size dev default -disk", vec![Size, Dev, Filesystem, Type, Used, Use, Free, MountPoint], ); check( "default-fs+inodes", vec![Type, Disk, Used, Use, Free, Size, MountPoint, InodesUse], ); check( "+inodes_used+inodes_free", vec![ Filesystem, Type, Disk, Used, Use, Free, Size, MountPoint, InodesUsed, InodesFree, ], ); } } dysk-cli-3.6.0/src/csv.rs000064400000000000000000000104651046102023000133070ustar 00000000000000use { crate::{ Args, col::Col, }, lfs_core::*, std::{ fmt::Display, io::Write, }, }; /// Utility to write in CSV struct Csv { separator: char, w: W, } impl Csv { pub fn new( separator: char, w: W, ) -> Self { Self { separator, w } } pub fn cell( &mut self, content: D, ) -> Result<(), std::io::Error> { let s = content.to_string(); let needs_quotes = s.contains(self.separator) || s.contains('"') || s.contains('\n'); if needs_quotes { write!(self.w, "\"")?; for c in s.chars() { if c == '"' { write!(self.w, "\"\"")?; } else { write!(self.w, "{}", c)?; } } write!(self.w, "\"")?; } else { write!(self.w, "{}", s)?; } write!(self.w, "{}", self.separator) } pub fn cell_opt( &mut self, content: Option, ) -> Result<(), std::io::Error> { if let Some(c) = content { self.cell(c) } else { write!(self.w, "{}", self.separator) } } pub fn end_line(&mut self) -> Result<(), std::io::Error> { writeln!(self.w) } } pub fn write( w: &mut W, mounts: &[&Mount], args: &Args, ) -> std::io::Result<()> { let units = args.units; let mut csv = Csv::new(args.csv_separator, w); for col in args.cols.cols() { csv.cell(col.title())?; } csv.end_line()?; for mount in mounts { for col in args.cols.cols() { match col { Col::Id => csv.cell_opt(mount.info.id), Col::Dev => csv.cell(&mount.info.dev), Col::Filesystem => csv.cell(&mount.info.fs), Col::Label => csv.cell_opt(mount.fs_label.as_ref()), Col::Type => csv.cell(&mount.info.fs_type), Col::Remote => csv.cell(if mount.is_remote() { "yes" } else { "no" }), Col::Disk => csv.cell_opt(mount.disk.as_ref().map(|d| d.disk_type())), Col::Used => csv.cell_opt(mount.stats().map(|s| units.fmt(s.used()))), Col::Use => csv.cell_opt(mount.stats().map(|s| s.use_share())), Col::UsePercent => csv.cell_opt( mount .stats() .map(|s| format!("{:.0}%", 100.0 * s.use_share())), ), Col::Free => csv.cell_opt(mount.stats().map(|s| units.fmt(s.available()))), Col::FreePercent => csv.cell_opt( mount .stats() .map(|s| format!("{:.0}%", 100.0 * (1.0 - s.use_share()))), ), Col::Size => csv.cell_opt(mount.stats().map(|s| units.fmt(s.size()))), Col::InodesUsed => csv.cell_opt(mount.inodes().map(|i| i.used())), Col::InodesUse => csv.cell_opt(mount.inodes().map(|i| i.use_share())), Col::InodesUsePercent => csv.cell_opt( mount .inodes() .map(|i| format!("{:.0}%", 100.0 * i.use_share())), ), Col::InodesFree => csv.cell_opt(mount.inodes().map(|i| i.favail)), Col::InodesCount => csv.cell_opt(mount.inodes().map(|i| i.files)), Col::MountPoint => csv.cell(mount.info.mount_point.to_string_lossy()), Col::Uuid => csv.cell(mount.uuid.as_ref().map_or("", |v| v)), Col::PartUuid => csv.cell(mount.part_uuid.as_ref().map_or("", |v| v)), Col::MountOptions => csv.cell(mount.info.options_string()), Col::CompressLevel => csv.cell_opt(mount.info.option_value("compress")), }?; } csv.end_line()?; } Ok(()) } #[test] fn test_csv() { use std::io::Cursor; let mut w = Cursor::new(Vec::new()); let mut csv = Csv::new(';', &mut w); csv.cell("1;2;3").unwrap(); csv.cell("\"").unwrap(); csv.cell("").unwrap(); csv.end_line().unwrap(); csv.cell(3).unwrap(); let s = String::from_utf8(w.into_inner()).unwrap(); assert_eq!( s, r#""1;2;3";"""";; 3;"#, ); } dysk-cli-3.6.0/src/filter.rs000064400000000000000000000044621046102023000140010ustar 00000000000000use { crate::col_expr::*, bet::*, lfs_core::*, std::str::FromStr, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum BoolOperator { And, Or, Not, } #[derive(Debug, Default, Clone)] pub struct Filter { expr: BeTree, } impl Filter { #[allow(clippy::match_like_matches_macro)] pub fn eval( &self, mount: &Mount, ) -> Result { self.expr .eval_faillible( // leaf evaluation |col_expr| col_expr.eval(mount), // bool operation |op, a, b| match (op, b) { (BoolOperator::And, Some(b)) => Ok(a & b), (BoolOperator::Or, Some(b)) => Ok(a | b), (BoolOperator::Not, None) => Ok(!a), _ => { unreachable!() } }, // when to short-circuit |op, a| match (op, a) { (BoolOperator::And, false) => true, (BoolOperator::Or, true) => true, _ => false, }, ) .map(|b| b.unwrap_or(true)) } pub fn filter<'m>( &self, mounts: &'m [Mount], ) -> Result, EvalExprError> { let mut filtered = Vec::new(); for mount in mounts { if self.eval(mount)? { filtered.push(mount); } } Ok(filtered) } } impl FromStr for Filter { type Err = ParseExprError; fn from_str(input: &str) -> Result { // we start by reading the global structure let mut expr: BeTree = BeTree::new(); for c in input.chars() { match c { '&' => expr.push_operator(BoolOperator::And), '|' => expr.push_operator(BoolOperator::Or), '!' => expr.push_operator(BoolOperator::Not), ' ' => {} '(' => expr.open_par(), ')' => expr.close_par(), _ => expr.mutate_or_create_atom(String::new).push(c), } } // then we parse each leaf let expr = expr.try_map_atoms(|raw| raw.parse())?; Ok(Self { expr }) } } dysk-cli-3.6.0/src/help.rs000064400000000000000000000046161046102023000134450ustar 00000000000000use { crate::args::*, clap::CommandFactory, }; static INTRO_TEMPLATE: &str = " **dysk** displays filesystem information in a pretty table. Complete documentation at https://dystroy.org/dysk "; static EXAMPLES_TEMPLATE: &str = " **Examples:** ${examples **${example-number})** ${example-title}: `${example-cmd}` ${example-comments} } "; static EXAMPLES: &[Example] = &[ Example::new("Standard overview of your usual disks", "dysk", ""), Example::new("List all filesystems", "dysk -a", ""), Example::new("Display inodes information", "dysk -c +inodes", ""), Example::new( "Add columns of your choice", "dysk -c label+dev+", "You may add columns before, after, or at any other place. \ You can change the column order too. \ See https://dystroy.org/dysk/table#columns\n", ), Example::new("See the disk of the current directory", "dysk .", ""), Example::new( "Filter for low space", "dysk -f 'use > 65% | free < 50G'", "", ), Example::new("Filter to exclude SSD disks", "dysk -f 'disk <> SSD'", ""), Example::new( "Complex filter", "dysk -f '(type=xfs & remote=no) | size > 5T'", "", ), Example::new("Export as JSON", "dysk -j", ""), Example::new( "Sort by free size", "dysk -s free", "Add `-desc` to the column name to sort in reverse.", ), ]; pub fn print(ascii: bool) { let mut printer = clap_help::Printer::new(Args::command()) .with("introduction", INTRO_TEMPLATE) .without("author"); printer.template_keys_mut().push("examples"); printer.set_template("examples", EXAMPLES_TEMPLATE); if ascii { printer.skin_mut().limit_to_ascii(); } for (i, example) in EXAMPLES.iter().enumerate() { printer .expander_mut() .sub("examples") .set("example-number", i + 1) .set("example-title", example.title) .set("example-cmd", example.cmd) .set_md("example-comments", example.comments); } printer.print_help(); } struct Example { title: &'static str, cmd: &'static str, comments: &'static str, } impl Example { pub const fn new( title: &'static str, cmd: &'static str, comments: &'static str, ) -> Self { Self { title, cmd, comments, } } } dysk-cli-3.6.0/src/json.rs000064400000000000000000000064551046102023000134710ustar 00000000000000use { crate::units::Units, lfs_core::*, serde_json::{ Value, json, }, }; pub fn output_value( mounts: &[&Mount], units: Units, ) -> Value { Value::Array( mounts .iter() .map(|mount| { let stats = mount.stats().map(|s| { let inodes = s.inodes.as_ref().map(|inodes| { json!({ "files": inodes.files, "free": inodes.ffree, "avail": inodes.favail, "used-percent": format!("{:.0}%", 100.0*inodes.use_share()), }) }); #[cfg(not(windows))] { json!({ "bsize": s.bsize, "blocks": s.blocks, "bused": s.bused, "bfree": s.bfree, "bavail": s.bavail, "size": units.fmt(s.size()), "used": units.fmt(s.used()), "used-percent": format!("{:.0}%", 100.0*s.use_share()), "available": units.fmt(s.available()), "inodes": inodes, }) } #[cfg(windows)] { json!({ "size": units.fmt(s.size()), "used": units.fmt(s.used()), "used-percent": format!("{:.0}%", 100.0*s.use_share()), "available": units.fmt(s.available()), "free": units.fmt(s.available()), "inodes": inodes, }) } }); let disk = mount.disk.as_ref().map(|d| { json!({ "type": d.disk_type(), "rotational": d.rotational, "removable": d.removable, "crypted": d.crypted, "ram": d.ram, }) }); let dev = { #[cfg(not(windows))] { json!({ "major": mount.info.dev.major, "minor": mount.info.dev.minor, }) } #[cfg(windows)] { mount.info.dev.to_string() } }; json!({ "id": mount.info.id, "dev": dev, "fs": mount.info.fs, "fs-label": mount.fs_label, "fs-type": mount.info.fs_type, "mount-point": mount.info.mount_point, "options": mount.info.options_string(), "disk": disk, "stats": stats, "bound": mount.info.bound, "remote": mount.is_remote(), "unreachable": mount.is_unreachable(), }) }) .collect(), ) } dysk-cli-3.6.0/src/lib.rs000064400000000000000000000057021046102023000132600ustar 00000000000000pub mod args; pub mod col; pub mod col_expr; pub mod cols; pub mod csv; pub mod filter; pub mod help; pub mod json; pub mod list_cols; pub mod normal; pub mod order; pub mod sorting; pub mod table; pub mod timeout; pub mod units; use { crate::{ args::*, normal::*, }, clap::Parser, std::io::{ self, Write, }, }; /// Print according to launch arguments /// /// # Errors /// Returns an `io::Error` if writing to stdout fails #[allow(clippy::match_like_matches_macro)] pub fn run() -> io::Result<()> { let mut w = io::stdout(); let args = Args::parse(); if args.version { return writeln!(&mut w, "dysk {}", env!("CARGO_PKG_VERSION")); } if args.help { help::print(args.ascii); if args.color() { csi_reset(); } return Ok(()); } if args.list_cols { list_cols::write(&mut w, args.color(), args.ascii)?; if args.color() { csi_reset(); } return Ok(()); } let mut options = lfs_core::ReadOptions::default() .remote_stats(args.remote_stats.unwrap_or_else(|| true)); if let Some(timeout) = args.timeout { options = options.stats_timeout(timeout.as_duration()); } if let Some(strategy) = &args.strategy { match strategy.parse() { Ok(strategy) => { options = options.strategy(strategy); } Err(_) => { eprintln!("Ignoring unrecognized strategy"); } } } let mut mounts = match lfs_core::read_mounts(&options) { Ok(mounts) => mounts, Err(e) => { eprintln!("Error reading mounts: {}", e); return Ok(()); } }; if !args.all { mounts.retain(is_normal); } if let Some(path) = &args.path { let dev = match lfs_core::DeviceId::of_path(path) { Ok(dev) => dev, Err(e) => { eprintln!("Error getting device of path {}: {}", path.display(), e); return Ok(()); } }; mounts.retain(|m| m.info.dev == dev); } args.sort.sort(&mut mounts); let mounts = match args.filter.clone().unwrap_or_default().filter(&mounts) { Ok(mounts) => mounts, Err(e) => { eprintln!("Error in filter evaluation: {}", e); return Ok(()); } }; if args.csv { return csv::write(&mut w, &mounts, &args); } if args.json { return writeln!( &mut w, "{}", serde_json::to_string_pretty(&json::output_value(&mounts, args.units)).unwrap() ); } if mounts.is_empty() { return writeln!(&mut w, "no mount to display - try\n dysk -a"); } table::write(&mut w, &mounts, args.color(), &args)?; if args.color() { csi_reset(); } Ok(()) } /// output a Reset CSI sequence fn csi_reset() { print!("\u{1b}[0m"); } dysk-cli-3.6.0/src/list_cols.rs000064400000000000000000000024311046102023000145010ustar 00000000000000use { crate::col::ALL_COLS, termimad::{ MadSkin, minimad::OwningTemplateExpander, }, std::io::{ self, Write, }, }; static MD: &str = r#" The `--cols` launch argument lets you specify the columns of the **dysk** table. You can give the explicit list of all columns: `dysk -c dev+fs` You can add columns to the default ones: `dysk -c +dev+size` Complete syntax at https://dystroy.org/dysk/table |:-:|:-:|:-:|:- |column | aliases | default | content |:-:|:-:|:-:|- ${column |${name}|${aliases}|${default}|${description} } |- "#; /// Print an help text describing columns pub fn write( w: &mut W, color: bool, ascii: bool, ) -> io::Result<()> { let mut expander = OwningTemplateExpander::new(); expander.set_default(""); for &col in ALL_COLS { expander .sub("column") .set("name", col.name()) .set("aliases", col.aliases().join(", ")) .set("default", if col.is_default() { "x" } else { "" }) .set("description", col.description()); } let mut skin = if color { MadSkin::default() } else { MadSkin::no_style() }; if ascii { skin.limit_to_ascii(); } skin.write_owning_expander_md(w, &expander, MD) } dysk-cli-3.6.0/src/normal.rs000064400000000000000000000023471046102023000140040ustar 00000000000000use { lfs_core::Mount, std::path::Path, }; /// Determine whether the mounted filesystem is "normal", which /// means it should be listed in standard pub fn is_normal(m: &Mount) -> bool { ( m.stats().is_some() || m.is_unreachable() ) && ( m.disk.is_some() // by default only fs with disks are shown || m.info.fs_type == "zfs" // unless it's zfs - see https://github.com/Canop/dysk/issues/32 || m.is_remote() ) && m.disk.as_ref().map_or(true, |d| !d.image) // not real && !m.info.bound // removing bound mounts && m.info.fs_type != "squashfs" // quite ad-hoc... && !is_system_path(&m.info.mount_point) } #[cfg(target_os = "macos")] fn is_system_path(path: &Path) -> bool { let Some(path) = path.to_str() else { return false; }; if path == "/" { return true; } if path.starts_with("/System") && !path.starts_with("/System/Volumes/Data") { return true; } if path.starts_with("/Library/Developer") { return true; } false } #[cfg(target_os = "linux")] fn is_system_path(path: &Path) -> bool { path.starts_with("/boot") } #[cfg(target_os = "windows")] fn is_system_path(_path: &Path) -> bool { false } dysk-cli-3.6.0/src/order.rs000064400000000000000000000020211046102023000136140ustar 00000000000000use std::{ fmt, str::FromStr, }; /// one of the two sorting directions #[derive(Debug, Clone, Copy, PartialEq)] pub enum Order { Asc, Desc, } #[derive(Debug)] pub struct ParseOrderError { /// the string which couldn't be parsed pub raw: String, } impl ParseOrderError { pub fn new>(s: S) -> Self { Self { raw: s.into() } } } impl fmt::Display for ParseOrderError { fn fmt( &self, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { write!( f, "{:?} can't be parsed as a sort order. Use 'asc' or 'desc' (or nothing)", self.raw ) } } impl std::error::Error for ParseOrderError {} impl FromStr for Order { type Err = ParseOrderError; fn from_str(s: &str) -> Result { let s = s.to_lowercase(); match s.as_ref() { "a" | "asc" => Ok(Self::Asc), "d" | "desc" => Ok(Self::Desc), _ => Err(ParseOrderError::new(s)), } } } dysk-cli-3.6.0/src/sorting.rs000064400000000000000000000042671046102023000142040ustar 00000000000000use { crate::{ col::Col, order::Order, }, lfs_core::Mount, std::{ error, fmt, str::FromStr, }, }; /// Sorting directive: the column and the order (asc or desc) #[derive(Debug, Clone, Copy, PartialEq)] pub struct Sorting { col: Col, order: Order, } impl Default for Sorting { fn default() -> Self { let col = Col::default_sort_col(); let order = col.default_sort_order(); Self { col, order } } } impl Sorting { pub fn sort( self, mounts: &mut [Mount], ) { let comparator = self.col.comparator(); mounts.sort_by(comparator); if self.order == Order::Desc { mounts.reverse(); } } } #[derive(Debug)] pub struct ParseSortingError { raw: String, reason: String, } impl ParseSortingError { pub fn new, E: ToString>( raw: S, reason: E, ) -> Self { Self { raw: raw.into(), reason: reason.to_string(), } } } impl fmt::Display for ParseSortingError { fn fmt( &self, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { write!( f, "{:?} can't be parsed as a sort expression because {}", self.raw, self.reason ) } } impl error::Error for ParseSortingError {} impl FromStr for Sorting { type Err = ParseSortingError; fn from_str(s: &str) -> Result { let cut_idx_len = s .char_indices() .find(|(_idx, c)| c.is_whitespace() || *c == '-') .map(|(idx, c)| (idx, c.len_utf8())); let (s_col, s_order) = match cut_idx_len { Some((idx, len)) => (&s[..idx], Some(&s[idx + len..])), None => (s, None), }; let col: Col = s_col .parse() .map_err(|pce| ParseSortingError::new(s, Box::new(pce)))?; let order = match s_order { Some(s_order) => s_order .parse() .map_err(|poe| ParseSortingError::new(s, Box::new(poe)))?, None => col.default_sort_order(), }; Ok(Self { col, order }) } } dysk-cli-3.6.0/src/table.rs000064400000000000000000000135121046102023000135770ustar 00000000000000use { crate::{ Args, col::Col, }, lfs_core::*, std::io::Write, termimad::{ CompoundStyle, MadSkin, ProgressBar, crossterm::style::Color::*, minimad::{ self, OwningTemplateExpander, TableBuilder, }, }, }; // those colors are chosen to be "redish" for used, "greenish" for available // and, most importantly, to work on both white and black backgrounds. If you // find a better combination, please show me. static USED_COLOR: u8 = 209; static AVAI_COLOR: u8 = 65; static SIZE_COLOR: u8 = 172; static BAR_WIDTH: usize = 5; static INODES_BAR_WIDTH: usize = 5; pub fn write( w: &mut W, mounts: &[&Mount], color: bool, args: &Args, ) -> std::io::Result<()> { if args.cols.is_empty() { return Ok(()); } let units = args.units; let mut expander = OwningTemplateExpander::new(); expander.set_default(""); for mount in mounts { let sub = expander .sub("rows") .set( "id", mount .info .id .as_ref() .map_or("".to_string(), |i| i.to_string()), ) .set("dev", mount.info.dev) .set("filesystem", &mount.info.fs) .set("disk", mount.disk.as_ref().map_or("", |d| d.disk_type())) .set("type", &mount.info.fs_type) .set("mount-point", mount.info.mount_point.to_string_lossy()) .set("mount-options", mount.info.options_string()) .set_option("uuid", mount.uuid.as_ref()) .set_option("part_uuid", mount.part_uuid.as_ref()) .set_option( "compress-level", mount.info.option_value("compress"), ); if let Some(label) = &mount.fs_label { sub.set("label", label); } if mount.is_remote() { sub.set("remote", "x"); } if let Some(stats) = mount.stats() { let use_share = stats.use_share(); let free_share = 1.0 - use_share; sub.set("size", units.fmt(stats.size())) .set("used", units.fmt(stats.used())) .set("use-percents", format!("{:.0}%", 100.0 * use_share)) .set_md("bar", progress_bar_md(use_share, BAR_WIDTH, args.ascii)) .set("free", units.fmt(stats.available())) .set("free-percents", format!("{:.0}%", 100.0 * free_share)); if let Some(inodes) = &stats.inodes { let iuse_share = inodes.use_share(); sub.set("inodes", inodes.files) .set("iused", inodes.used()) .set("iuse-percents", format!("{:.0}%", 100.0 * iuse_share)) .set_md( "ibar", progress_bar_md(iuse_share, INODES_BAR_WIDTH, args.ascii), ) .set("ifree", inodes.favail); } } else if mount.is_timeout() { sub.set("use-error", "timeout"); } else if mount.is_unreachable() { sub.set("use-error", "unreachable"); } } let mut skin = if color { make_colored_skin() } else { MadSkin::no_style() }; if args.ascii { skin.limit_to_ascii(); } let mut tbl = TableBuilder::default(); for col in args.cols.cols() { tbl.col( minimad::Col::new( col.title(), match col { Col::Id => "${id}", Col::Dev => "${dev}", Col::Filesystem => "${filesystem}", Col::Label => "${label}", Col::Disk => "${disk}", Col::Type => "${type}", Col::Remote => "${remote}", Col::Used => "~~${used}~~", Col::Use => "~~${use-percents}~~ ${bar}~~${use-error}~~", Col::UsePercent => "~~${use-percents}~~", Col::Free => "*${free}*", Col::FreePercent => "*${free-percents}*", Col::Size => "**${size}**", Col::InodesFree => "*${ifree}*", Col::InodesUsed => "~~${iused}~~", Col::InodesUse => "~~${iuse-percents}~~ ${ibar}", Col::InodesUsePercent => "~~${iuse-percents}~~", Col::InodesCount => "**${inodes}**", Col::MountPoint => "${mount-point}", Col::Uuid => "${uuid}", Col::PartUuid => "${part_uuid}", Col::MountOptions => "${mount-options}", Col::CompressLevel => "${compress-level}", }, ) .align_content(col.content_align()) .align_header(col.header_align()), ); } skin.write_owning_expander_md(w, &expander, &tbl) } fn make_colored_skin() -> MadSkin { MadSkin { bold: CompoundStyle::with_fg(AnsiValue(SIZE_COLOR)), // size inline_code: CompoundStyle::with_fgbg(AnsiValue(USED_COLOR), AnsiValue(AVAI_COLOR)), // use bar strikeout: CompoundStyle::with_fg(AnsiValue(USED_COLOR)), // use% italic: CompoundStyle::with_fg(AnsiValue(AVAI_COLOR)), // available ..Default::default() } } fn progress_bar_md( share: f64, bar_width: usize, ascii: bool, ) -> String { if ascii { let count = (share * bar_width as f64).round() as usize; let bar: String = "".repeat(count); let no_bar: String = "-".repeat(bar_width - count); format!("~~{}~~*{}*", bar, no_bar) } else { let pb = ProgressBar::new(share as f32, bar_width); format!("`{:); impl Timeout { pub fn as_duration(&self) -> Option { self.0 } fn try_read(s: &str) -> Option { if s == "none" || s == "no" { return Some(Self(None)); } if let Some(n) = s.strip_suffix("ms") { if let Ok(n) = n.parse::() { return Some(Self(Some(Duration::from_millis(n)))); } } else if let Some(n) = s.strip_suffix("s") { if let Ok(n) = n.parse::() { return Some(Self(Some(Duration::from_secs(n)))); } } None } } impl FromStr for Timeout { type Err = &'static str; fn from_str(s: &str) -> Result { Self::try_read(s) .ok_or(r#"Invalid timeout, expected "none" or [s|ms]"#) } } dysk-cli-3.6.0/src/units.rs000064400000000000000000000060321046102023000136510ustar 00000000000000use core::str::FromStr; /// The Units system used for sizes #[derive(Debug, Clone, Copy, PartialEq)] pub enum Units { Si, // Units according to the SI system, based on multiples of 1000 Binary, // Old binary based units, based on multiples of 1024 Bytes, // Just the raw byte counts, with commas separating thousands } impl Default for Units { fn default() -> Self { Self::Si } } impl FromStr for Units { type Err = String; fn from_str(value: &str) -> Result { match value.to_lowercase().as_ref() { "si" => Ok(Self::Si), "binary" => Ok(Self::Binary), "bytes" => Ok(Self::Bytes), _ => Err(format!( "Illegal value: {:?} - valid values are 'SI', 'binary', and 'bytes'", value )), } } } static PREFIXES: &[char] = &['K', 'M', 'G', 'T', 'P']; impl Units { pub fn fmt( self, size: u64, ) -> String { match self { Self::Si => file_size::fit_4(size), Self::Binary => { if size < 10_000 { size.to_string() } else { let i = size.ilog2() / 10u32; let idx = i as usize - 1; let size = size as f64; if idx >= PREFIXES.len() { "huge".to_string() } else { let v = size / (1024u64.pow(i) as f64); if v >= 10f64 { format!("{:.0}{}i", v.round(), PREFIXES[idx]) } else { format!("{:.1}{}i", v, PREFIXES[idx]) } } } } Self::Bytes => { let mut rev: Vec = Vec::new(); for (i, c) in size.to_string().chars().rev().enumerate() { if i > 0 && i % 3 == 0 { rev.push(','); } rev.push(c); } rev.drain(..).rev().collect() } } } } #[test] fn test_fmt_binary() { fn check( v: u64, s: &str, ) { assert_eq!(&Units::Binary.fmt(v), s); } check(0, "0"); check(1, "1"); check(456, "456"); check(1456, "1456"); check(9_999, "9999"); check(10_000, "9.8Ki"); check(12_345, "12Ki"); check(123_456, "121Ki"); check(1_000_000_000, "954Mi"); check(1_073_741_824, "1.0Gi"); check(1_234_567_890, "1.1Gi"); } #[test] fn test_fmt_bytes() { fn check( v: u64, s: &str, ) { assert_eq!(&Units::Bytes.fmt(v), s); } check(0, "0"); check(1, "1"); check(456, "456"); check(1456, "1,456"); check(9_999, "9,999"); check(10_000, "10,000"); check(12_345, "12,345"); check(123_456, "123,456"); check(1_234_567, "1,234,567"); check(1_000_000_000, "1,000,000,000"); check(1_234_567_890, "1,234,567,890"); }