From 55a96a114d181cda9d3dfecf553908ea8e9b102d Mon Sep 17 00:00:00 2001 From: Kai Moschcau Date: Thu, 24 Sep 2020 20:09:26 +0200 Subject: [PATCH] Add ability to configure default options with file # Conflicts: # Cargo.lock # Cargo.toml # README.md # src/app.rs # src/core.rs # src/display.rs # src/flags.rs # src/meta/mod.rs # src/sort.rs # tests/integration.rs --- Cargo.lock | 592 +++++++++++++++++++++----------------- Cargo.toml | 5 +- README.md | 141 ++++++++- src/app.rs | 8 +- src/config_file.rs | 175 +++++++++++ src/core.rs | 20 +- src/display.rs | 12 +- src/flags.rs | 530 +++++++--------------------------- src/flags/blocks.rs | 538 ++++++++++++++++++++++++++++++++++ src/flags/color.rs | 215 ++++++++++++++ src/flags/date.rs | 222 ++++++++++++++ src/flags/dereference.rs | 107 +++++++ src/flags/display.rs | 167 +++++++++++ src/flags/icons.rs | 356 +++++++++++++++++++++++ src/flags/ignore_globs.rs | 188 ++++++++++++ src/flags/indicators.rs | 107 +++++++ src/flags/layout.rs | 163 +++++++++++ src/flags/recursion.rs | 337 ++++++++++++++++++++++ src/flags/size.rs | 159 ++++++++++ src/flags/sorting.rs | 523 +++++++++++++++++++++++++++++++++ src/flags/symlinks.rs | 104 +++++++ src/flags/total_size.rs | 104 +++++++ src/main.rs | 12 +- src/meta/indicator.rs | 14 +- src/meta/mod.rs | 13 +- src/sort.rs | 46 +-- tests/integration.rs | 65 ++++- 27 files changed, 4176 insertions(+), 747 deletions(-) create mode 100644 src/config_file.rs create mode 100644 src/flags/blocks.rs create mode 100644 src/flags/color.rs create mode 100644 src/flags/date.rs create mode 100644 src/flags/dereference.rs create mode 100644 src/flags/display.rs create mode 100644 src/flags/icons.rs create mode 100644 src/flags/ignore_globs.rs create mode 100644 src/flags/indicators.rs create mode 100644 src/flags/layout.rs create mode 100644 src/flags/recursion.rs create mode 100644 src/flags/size.rs create mode 100644 src/flags/sorting.rs create mode 100644 src/flags/symlinks.rs create mode 100644 src/flags/total_size.rs diff --git a/Cargo.lock b/Cargo.lock index c835005..d50dbf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,664 +2,730 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.10" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + [[package]] name = "assert_cmd" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c88b9ca26f9c16ec830350d309397e74ee9abdfd8eb1f71cb6ecc71a3fc818da" dependencies = [ - "doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", ] [[package]] name = "assert_fs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dabd011e19821a348abb0dec7b7fda959cd6b3477c474395b958b291942b0e" dependencies = [ - "doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "globwalk 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", + "winapi", ] [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] [[package]] name = "bstr" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chrono" -version = "0.4.11" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b" dependencies = [ - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer", + "num-traits", + "time", ] [[package]] name = "chrono-humanize" version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2ff48a655fe8d2dae9a39e66af7fd8ff32a879e8c4e27422c25596a8b5e90d" dependencies = [ - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono", ] [[package]] name = "clap" -version = "2.33.0" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.11.0", + "atty", + "bitflags", + "strsim", + "term_size", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] -name = "crossbeam-channel" -version = "0.4.2" +name = "constant_time_eq" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "crossbeam-utils" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "cfg-if", + "lazy_static", ] [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "dirs" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi", +] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "float-cmp" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" dependencies = [ - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits", ] [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120" dependencies = [ - "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", - "bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", ] [[package]] name = "globwalk" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9db17aec586697a93219b19726b5b68307eba92898c34b170857343fe67c99d" dependencies = [ - "ignore 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ignore", + "walkdir", ] [[package]] name = "hermit-abi" -version = "0.1.10" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151" dependencies = [ - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "human-sort" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219" [[package]] name = "ignore" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22dcbf2a4a289528dbef21686354904e1c694ac642610a9bff9e7df730d9ec72" dependencies = [ - "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.68" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" + +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", ] [[package]] name = "lscolors" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e" dependencies = [ - "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.12.1", ] [[package]] name = "lsd" version = "0.18.0" dependencies = [ - "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "assert_cmd 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "assert_fs 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "human-sort 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "terminal_size 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wild 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.12.1", + "assert_cmd", + "assert_fs", + "chrono-humanize", + "clap", + "dirs", + "globset", + "human-sort", + "libc", + "lscolors", + "predicates", + "tempfile", + "term_grid", + "terminal_size", + "time", + "unicode-width", + "users", + "version_check", + "wild", + "winapi", + "xdg", + "yaml-rust", ] -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-integer" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "num-traits", ] [[package]] name = "num-traits" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" [[package]] name = "predicates" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a" dependencies = [ - "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "float-cmp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "normalize-line-endings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "difference", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", ] [[package]] name = "predicates-core" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" [[package]] name = "predicates-tree" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" dependencies = [ - "predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "predicates-core", + "treeline", ] [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86", + "rand_core", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core", ] [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] [[package]] name = "regex" -version = "1.3.6" +version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ - "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", ] [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" [[package]] name = "remove_dir_all" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", +] + +[[package]] +name = "rust-argon2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", ] [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", ] [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] name = "term_grid" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230d3e804faaed5a39b08319efb797783df2fd9671b39b7596490cb486d702cf" dependencies = [ - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "term_size" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winapi", ] [[package]] name = "terminal_size" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a14cd9f8c72704232f0bfc8455c0e861f0ad4eb60cc9ec8a170e231414c1e13" dependencies = [ - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "winapi", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "term_size", + "unicode-width", ] [[package]] name = "thread_local" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", ] [[package]] name = "time" -version = "0.1.42" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", ] [[package]] name = "treeline" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "users" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486" dependencies = [ - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "log", ] [[package]] name = "vec_map" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ - "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "walkdir" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ - "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "same-file", + "winapi", + "winapi-util", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wild" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020" dependencies = [ - "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glob", ] [[package]] name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[metadata] -"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -"checksum assert_cmd 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c88b9ca26f9c16ec830350d309397e74ee9abdfd8eb1f71cb6ecc71a3fc818da" -"checksum assert_fs 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04dabd011e19821a348abb0dec7b7fda959cd6b3477c474395b958b291942b0e" -"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" -"checksum chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2ff48a655fe8d2dae9a39e66af7fd8ff32a879e8c4e27422c25596a8b5e90d" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" -"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" -"checksum doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -"checksum float-cmp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "da62c4f1b81918835a8c6a484a397775fff5953fe83529afd51b05f5c6a6617d" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -"checksum globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120" -"checksum globwalk 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d9db17aec586697a93219b19726b5b68307eba92898c34b170857343fe67c99d" -"checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" -"checksum human-sort 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219" -"checksum ignore 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf60d063dbe6b75388eec66cfc07781167ae3d34a09e0c433e6c5de0511f7fb" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f77452267149eac960ded529fe5f5460ddf792845a1d71b5d0cfcee5642e47e" -"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -"checksum normalize-line-endings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" -"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" -"checksum predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "347a1b6f0b21e636bc9872fb60b83b8e185f6f5516298b8238699f7f9a531030" -"checksum predicates-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" -"checksum predicates-tree 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" -"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" -"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" -"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum term_grid 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "230d3e804faaed5a39b08319efb797783df2fd9671b39b7596490cb486d702cf" -"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327" -"checksum terminal_size 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8038f95fc7a6f351163f4b964af631bd26c9e828f7db085f2a84aca56f70d13b" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" -"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" -"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -"checksum wild 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "97d34fecce28871e5c0e059deae21ef7f7d13b98a5964b24c58b3735c8052fc8" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "xdg" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66b7c2281ebde13cf4391d70d4c7e5946c3c25e72a7b859ca8f677dcd0b0c61" + +[[package]] +name = "yaml-rust" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 2ebcccf..ef8d591 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,9 @@ time = "0.1.*" [dependencies] ansi_term = "0.12.*" +dirs = "3.0.*" libc = "0.2.*" +human-sort = "0.2.2" term_grid = "0.1.*" terminal_size = "0.1.*" time = "0.1.*" @@ -31,7 +33,8 @@ unicode-width = "0.1.*" lscolors = "0.7" wild = "2.0.*" globset = "0.4.*" -human-sort = "0.2.2" +xdg = "2.1.*" +yaml-rust = "0.4.*" [target.'cfg(unix)'.dependencies] users = "0.10.*" diff --git a/README.md b/README.md index c7b23e6..dcee416 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ - [Description](#description) - [Screenshot](#screenshot) - [Installation](#installation) -- [Configurations](#configurations) +- [Configuration](#configuration) +- [External Configurations](#external-configurations) * [Required](#required) * [Optional](#optional) - [F.A.Q.](#faq) @@ -120,7 +121,143 @@ cargo install --git https://github.com/Peltoche/lsd.git --branch master The [release page](https://github.com/Peltoche/lsd/releases) includes precompiled binaries for Linux and macOS. -## Configurations +## Configuration + +`lsd` can be configured with a configuration file to set the default options. +Right now this only supports setting options that can be passed via the command +line options as well. + +### Config file location + +#### Non-Windows + +On non-Windows systems `lsd` follows the +[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) +convention for the location of the configuration file. The configuration dir +`lsd` uses is itself named `lsd`. In that directory it looks first for a file +called `config.yaml` and if it can't find one, a file named `config.yml`. +For most people it should be enough to put their config file at +`~/.config/lsd/config.yaml`. + +#### Windows + +On Windows systems `lsd` only looks for the two files in one location: +`%APPDATA%\lsd\` + +### Config file content + +This is an example config file with the default values and some additional +remarks. +```yaml +# == Classic == +# This is a shorthand to override some of the options to be backwards compatible +# with `ls`. It affects the "color"->"when", "sorting"->"dir-grouping", "date" +# and "icons"->"when" options. +# Possible values: false, true +classic: false + +# == Blocks == +# This specifies the columns and their order when using the long and the tree +# layout. +# Possible values: permission, user, group, size, size_value, date, name, inode +blocks: + - permission + - user + - group + - size + - date + - name + +# == Color == +# This has various color options. (Will be expanded in the future.) +color: + # When to colorize the output. + # When "classic" is set, this is set to "never". + # Possible values: never, auto, always + when: auto + +# == Date == +# This specifies the date format for the date column. The freeform format +# accepts an strftime like string. +# When "classic" is set, this is set to "date". +# Possible values: date, relative, + +date: date + +# == Dereference == +# Whether to dereference symbolic links. +# Possible values: false, true +dereference: false + +# == Display == +# What items to display. Do not specify this for the default behavior. +# Possible values: all, almost-all, directory-only +# display: all + +# == Icons == +icons: + # When to use icons. + # When "classic" is set, this is set to "never". + # Possible values: always, auto, never + when: auto + # Which icon theme to use. + # Possible values: fancy, unicode + theme: fancy + +# == Ignore Globs == +# A list of globs to ignore when listing. +# ignore-globs: +# - .git + +# == Indicators == +# Whether to add indicator characters to certain listed files. +# Possible values: false, true +indicators: false + +# == Layout == +# Which layout to use. "oneline" might be a bit confusing here and should be +# called "one-per-line". It might be changed in the future. +# Possible values: grid, tree, oneline +layout: grid + +# == Recursion == +recursion: + # Whether to enable recursion. + # Possible values: false, true + enabled: false + # How deep the recursion should go. This has to be a positive integer. Leave + # it unspecified for (virtually) infinite. + # depth: 3 + +# == Size == +# Specifies the format of the size column. +# Possible values: default, short, bytes +size: default + +# == Sorting == +sorting: + # Specify what to sort by. + # Possible values: extension, name, time, size, version + column: name + # Whether to reverse the sorting. + # Possible values: false, true + reverse: false + # Whether to group directories together and where. + # When "classic" is set, this is set to "none". + # Possible values: first, last, none + dir-grouping: none + +# == No Symlink == +# Whether to omit showing symlink targets +# Possible values: false, true +no-symlink: false + +# == Total size == +# Whether to display the total size of directories. +# Possible values: false, true +total-size: false +``` + +## External Configurations ### Required diff --git a/src/app.rs b/src/app.rs index e6a4fb7..dc0b78d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -67,6 +67,11 @@ pub fn build() -> App<'static, 'static> { .multiple(true) .help("Display extended file metadata as a table"), ) + .arg( + Arg::with_name("no-config") + .long("no-config") + .help("Do not read a configuration file"), + ) .arg( Arg::with_name("oneline") .short("1") @@ -173,6 +178,7 @@ pub fn build() -> App<'static, 'static> { .arg( Arg::with_name("versionsort") .short("v") + .long("versionsort") .multiple(true) .overrides_with("timesort") .overrides_with("sizesort") @@ -261,7 +267,7 @@ fn validate_date_argument(arg: String) -> Result<(), String> { } } -fn validate_time_format(formatter: &str) -> Result<(), time::ParseError> { +pub fn validate_time_format(formatter: &str) -> Result<(), time::ParseError> { let mut chars = formatter.chars(); loop { match chars.next() { diff --git a/src/config_file.rs b/src/config_file.rs new file mode 100644 index 0000000..b52db2a --- /dev/null +++ b/src/config_file.rs @@ -0,0 +1,175 @@ +///! This module provides methods to handle the program's config files and operations related to +///! this. +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; + +use crate::print_error; + +#[cfg(not(windows))] +use xdg::BaseDirectories; +use yaml_rust::{Yaml, YamlLoader}; + +const CONF_DIR: &str = "lsd"; +const CONF_FILE_NAME: &str = "config"; +const YAML_LONG_EXT: &str = "yaml"; +const YAML_SHORT_EXT: &str = "yml"; + +/// A struct to hold an optional file path [String] and an optional [Yaml], and provides methods +/// around error handling in a config file. +#[derive(Clone, Debug)] +pub struct Config { + pub file: Option, + pub yaml: Option, +} + +impl Config { + /// This constructs a Config struct without a file [String] and without a [Yaml]. + pub fn with_none() -> Self { + Self { + file: None, + yaml: None, + } + } + + /// This constructs a Config struct with a passed file [String] and without a [Yaml]. + pub fn with_file(file: String) -> Self { + Self { + file: Some(file), + yaml: None, + } + } + + /// This constructs a Config struct with a passed [Yaml] and without a file [String]. + #[cfg(test)] + pub fn with_yaml(yaml: Yaml) -> Self { + Self { + file: None, + yaml: Some(yaml), + } + } + + /// This tries to read a configuration file like in the XDG_BASE_DIRS specification and returns + /// the contents of the YAML config file. + pub fn read_config() -> Self { + let config_file_long_path; + let config_file_short_path; + match Self::config_file_paths() { + Some((long, short)) => { + config_file_long_path = long; + config_file_short_path = short; + } + _ => return Self::with_none(), + } + + let mut out_config; + let mut config_file; + match File::open(&config_file_long_path) { + Ok(result) => { + config_file = result; + out_config = Self::with_file(config_file_long_path.as_path().display().to_string()); + } + Err(_) => match File::open(&config_file_short_path) { + Ok(result) => { + config_file = result; + out_config = + Self::with_file(config_file_short_path.as_path().display().to_string()); + } + Err(_) => return Self::with_none(), + }, + } + + let mut config_content = String::new(); + if let Err(error) = config_file.read_to_string(&mut config_content) { + print_error!("Found a config file, but could not read it: {}", error); + return out_config; + } + + match YamlLoader::load_from_str(&config_content) { + Ok(result) => { + if !result.is_empty() { + out_config.yaml = Some(result[0].clone()); + } + out_config + } + Err(error) => { + print_error!("Error parsing config: {}\n", error); + out_config + } + } + } + + /// This provides two paths for a configuration file (the first with the long yaml extension, + /// the second with the short yml extension), according to the XDG_BASE_DIRS specification. + #[cfg(not(windows))] + pub fn config_file_paths() -> Option<(PathBuf, PathBuf)> { + let base_dirs; + match BaseDirectories::with_prefix(CONF_DIR) { + Ok(result) => base_dirs = result, + _ => return None, + } + + let config_file_long_path; + match base_dirs.place_config_file([CONF_FILE_NAME, YAML_LONG_EXT].join(".")) { + Ok(result) => config_file_long_path = result, + _ => return None, + } + + let config_file_short_path; + match base_dirs.place_config_file([CONF_FILE_NAME, YAML_SHORT_EXT].join(".")) { + Ok(result) => config_file_short_path = result, + _ => return None, + } + + Some((config_file_long_path, config_file_short_path)) + } + + /// This provides two paths for a configuration file (the first with the long yaml extension, + /// the second with the short yml extension) inside the %APPDATA% directory. + #[cfg(windows)] + pub fn config_file_paths() -> Option<(PathBuf, PathBuf)> { + let mut config_file_long_path; + match dirs::config_dir() { + Some(path) => config_file_long_path = path, + _ => return None, + } + + config_file_long_path.push(CONF_DIR); + let mut config_file_short_path = config_file_long_path.clone(); + + config_file_long_path.push([CONF_FILE_NAME, YAML_LONG_EXT].join(".")); + config_file_short_path.push([CONF_FILE_NAME, YAML_SHORT_EXT].join(".")); + + Some((config_file_long_path, config_file_short_path)) + } + + /// Returns whether the Config has a [Yaml]. + pub fn has_yaml(&self) -> bool { + self.yaml.is_some() + } + + /// This prints the provided warning message to stderr, prepending the executable name and the + /// configuration file path that likely caused the warning. + pub fn print_warning(&self, message: &str) { + print_error!( + "lsd: {} - {}\n", + self.file.as_ref().unwrap_or(&String::from("")), + message + ); + } + + /// This prints a predetermined warning message to stderr, warning about an invalid value for a + /// configuration element. + pub fn print_invalid_value_warning(&self, name: &str, value: &str) { + self.print_warning(&format!("Not a valid {} value: {}", name, value)); + } + + /// This prints a predetermined warning message to stderr, warning about a wrong [Yaml] data + /// type for a configuration value. + pub fn print_wrong_type_warning(&self, name: &str, type_name: &str) { + self.print_warning(&format!( + "The {} config value has to be a {}.", + name, type_name + )); + } +} diff --git a/src/core.rs b/src/core.rs index 026f505..92cea8f 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,6 +1,6 @@ use crate::color::{self, Colors}; use crate::display; -use crate::flags::{Display, Flags, IconTheme, Layout, SortOrder, WhenFlag}; +use crate::flags::{ColorOption, Display, Flags, IconOption, IconTheme, Layout, SortOrder}; use crate::icon::{self, Icons}; use crate::meta::Meta; use crate::{print_error, print_output, sort}; @@ -40,13 +40,13 @@ impl Core { let mut inner_flags = flags.clone(); - let color_theme = match (tty_available && console_color_ok, flags.color) { - (_, WhenFlag::Never) | (false, WhenFlag::Auto) => color::Theme::NoColor, + let color_theme = match (tty_available && console_color_ok, flags.color.when) { + (_, ColorOption::Never) | (false, ColorOption::Auto) => color::Theme::NoColor, _ => color::Theme::Default, }; - let icon_theme = match (tty_available, flags.icon, flags.icon_theme) { - (_, WhenFlag::Never, _) | (false, WhenFlag::Auto, _) => icon::Theme::NoIcon, + let icon_theme = match (tty_available, flags.icons.when, flags.icons.theme) { + (_, IconOption::Never, _) | (false, IconOption::Auto, _) => icon::Theme::NoIcon, (_, _, IconTheme::Fancy) => icon::Theme::Fancy, (_, _, IconTheme::Unicode) => icon::Theme::Unicode, }; @@ -80,13 +80,13 @@ impl Core { fn fetch(&self, paths: Vec) -> Vec { let mut meta_list = Vec::with_capacity(paths.len()); let depth = match self.flags.layout { - Layout::Tree { .. } => self.flags.recursion_depth, - _ if self.flags.recursive => self.flags.recursion_depth, + Layout::Tree { .. } => self.flags.recursion.depth, + _ if self.flags.recursion.enabled => self.flags.recursion.depth, _ => 1, }; for path in paths { - let mut meta = match Meta::from_path(&path, self.flags.dereference) { + let mut meta = match Meta::from_path(&path, self.flags.dereference.0) { Ok(meta) => meta, Err(err) => { print_error!("lsd: {}: {}\n", path.display(), err); @@ -95,7 +95,7 @@ impl Core { }; match self.flags.display { - Display::DisplayDirectoryItself => { + Display::DirectoryItself => { meta_list.push(meta); } _ => { @@ -112,7 +112,7 @@ impl Core { } }; } - if self.flags.total_size { + if self.flags.total_size.0 { for meta in &mut meta_list.iter_mut() { meta.calculate_total_size(); } diff --git a/src/display.rs b/src/display.rs index 13155ac..9557b4f 100644 --- a/src/display.rs +++ b/src/display.rs @@ -61,7 +61,7 @@ fn inner_display_grid( // The first iteration (depth == 0) corresponds to the inputs given by the // user. We defer displaying directories given by the user unless we've been // asked to display the directory itself (rather than its contents). - let skip_dirs = (depth == 0) && (flags.display != Display::DisplayDirectoryItself); + let skip_dirs = (depth == 0) && (flags.display != Display::DirectoryItself); // print the files first. for meta in metas { @@ -107,7 +107,7 @@ fn inner_display_grid( output += &grid.fit_into_columns(1).to_string(); } } else { - output += &grid.fit_into_columns(flags.blocks.len()).to_string(); + output += &grid.fit_into_columns(flags.blocks.0.len()).to_string(); } let should_display_folder_path = should_display_folder_path(depth, &metas, &flags); @@ -174,7 +174,7 @@ fn inner_display_tree( } } - let content = grid.fit_into_columns(flags.blocks.len()).to_string(); + let content = grid.fit_into_columns(flags.blocks.0.len()).to_string(); let mut lines = content.lines(); for (idx, meta) in metas.iter().enumerate() { @@ -254,7 +254,7 @@ fn get_output<'a>( padding_rules: &HashMap, ) -> Vec> { let mut strings: Vec = Vec::new(); - for block in flags.blocks.iter() { + for block in flags.blocks.0.iter() { match block { Block::INode => strings.push(meta.inode.render(colors)), Block::Permission => { @@ -276,7 +276,7 @@ fn get_output<'a>( Block::Date => strings.push(meta.date.render(colors, &flags)), Block::Name => { let s: String = - if flags.no_symlink || flags.dereference || flags.layout == Layout::Grid { + if flags.no_symlink.0 || flags.dereference.0 || flags.layout == Layout::Grid { ANSIStrings(&[ meta.name.render(colors, icons, &display_option), meta.indicator.render(&flags), @@ -332,7 +332,7 @@ fn detect_size_lengths(metas: &[Meta], flags: &Flags) -> usize { fn get_padding_rules(metas: &[Meta], flags: &Flags) -> HashMap { let mut padding_rules: HashMap = HashMap::new(); - if flags.blocks.contains(&Block::Size) { + if flags.blocks.0.contains(&Block::Size) { let size_val = detect_size_lengths(&metas, &flags); padding_rules.insert(Block::SizeValue, size_val); diff --git a/src/flags.rs b/src/flags.rs index fc430b2..1f7b478 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -1,441 +1,127 @@ -use clap::{ArgMatches, Error, ErrorKind}; -use globset::{Glob, GlobSet, GlobSetBuilder}; -use std::iter::Iterator; +pub mod blocks; +pub mod color; +pub mod date; +pub mod dereference; +pub mod display; +pub mod icons; +pub mod ignore_globs; +pub mod indicators; +pub mod layout; +pub mod recursion; +pub mod size; +pub mod sorting; +pub mod symlinks; +pub mod total_size; -#[derive(Clone, Debug)] +pub use blocks::Block; +pub use blocks::Blocks; +pub use color::Color; +pub use color::ColorOption; +pub use date::DateFlag; +pub use dereference::Dereference; +pub use display::Display; +pub use icons::IconOption; +pub use icons::IconTheme; +pub use icons::Icons; +pub use ignore_globs::IgnoreGlobs; +pub use indicators::Indicators; +pub use layout::Layout; +pub use recursion::Recursion; +pub use size::SizeFlag; +pub use sorting::DirGrouping; +pub use sorting::SortColumn; +pub use sorting::SortOrder; +pub use sorting::Sorting; +pub use symlinks::NoSymlink; +pub use total_size::TotalSize; + +use crate::config_file::Config; + +use clap::{ArgMatches, Error}; + +#[cfg(doc)] +use yaml_rust::Yaml; + +/// A struct to hold all set configuration flags for the application. +#[derive(Clone, Debug, Default)] pub struct Flags { - pub display: Display, - pub layout: Layout, - pub display_indicators: bool, - pub recursive: bool, - pub sort_by: SortFlag, - pub sort_order: SortOrder, - pub directory_order: DirOrderFlag, - pub size: SizeFlag, + pub blocks: Blocks, + pub color: Color, pub date: DateFlag, - pub color: WhenFlag, - pub icon: WhenFlag, - pub icon_theme: IconTheme, - pub inode: bool, - pub recursion_depth: usize, - pub blocks: Vec, - pub no_symlink: bool, - pub total_size: bool, - pub ignore_globs: GlobSet, - pub dereference: bool, + pub dereference: Dereference, + pub display: Display, + pub display_indicators: Indicators, + pub icons: Icons, + pub ignore_globs: IgnoreGlobs, + pub layout: Layout, + pub no_symlink: NoSymlink, + pub recursion: Recursion, + pub size: SizeFlag, + pub sorting: Sorting, + pub total_size: TotalSize, } impl Flags { - pub fn from_matches(matches: &ArgMatches) -> Result { - let classic_mode = matches.is_present("classic"); - let color_inputs: Vec<&str> = matches.values_of("color").unwrap().collect(); - let icon_inputs: Vec<&str> = matches.values_of("icon").unwrap().collect(); - let icon_theme_inputs: Vec<&str> = matches.values_of("icon-theme").unwrap().collect(); - let size_inputs: Vec<&str> = matches.values_of("size").unwrap().collect(); - let date_inputs: Vec<&str> = matches.values_of("date").unwrap().collect(); - let dir_order_inputs: Vec<&str> = matches.values_of("group-dirs").unwrap().collect(); - let ignore_globs_inputs: Vec<&str> = matches.values_of("ignore-glob").unwrap().collect(); - let dereference = matches.is_present("dereference"); - // inode set layout to oneline and blocks to inode,name - let inode = matches.is_present("inode"); - let blocks_inputs: Vec<&str> = if let Some(blocks) = matches.values_of("blocks") { - blocks.collect() - } else { - vec![] - }; - - let display = if matches.is_present("all") { - Display::DisplayAll - } else if matches.is_present("almost-all") { - Display::DisplayAlmostAll - } else if matches.is_present("directory-only") { - Display::DisplayDirectoryItself - } else { - Display::DisplayOnlyVisible - }; - - let sort_by = if matches.is_present("timesort") { - SortFlag::Time - } else if matches.is_present("sizesort") { - SortFlag::Size - } else if matches.is_present("extensionsort") { - SortFlag::Extension - } else if matches.is_present("versionsort") { - SortFlag::Version - } else { - SortFlag::Name - }; - - let sort_order = if matches.is_present("reverse") { - SortOrder::Reverse - } else { - SortOrder::Default - }; - - let layout = if matches.is_present("tree") { - Layout::Tree - } else if matches.is_present("long") - || matches.is_present("oneline") - || blocks_inputs.len() > 1 - || inode - { - Layout::OneLine - } else { - Layout::Grid - }; - - let recursive = matches.is_present("recursive"); - let recursion_input = matches.values_of("depth").and_then(Iterator::last); - let recursion_depth = match recursion_input { - Some(str) if recursive || layout == Layout::Tree => match str.parse::() { - Ok(val) => val, - Err(_) => { - return Err(Error::with_description( - "The argument '--depth' requires a valid positive number", - ErrorKind::ValueValidation, - )); - } - }, - Some(_) => { - return Err(Error::with_description( - "The argument '--depth' requires '--tree' or '--recursive'", - ErrorKind::MissingRequiredArgument, - )); - } - None => usize::max_value(), - }; - - let mut blocks: Vec = if !blocks_inputs.is_empty() { - blocks_inputs.into_iter().map(Block::from).collect() - } else if matches.is_present("long") { - vec![ - Block::Permission, - Block::User, - Block::Group, - Block::Size, - Block::Date, - Block::Name, - ] - } else { - vec![Block::Name] - }; - - // Add inode as first column if with inode flag - if inode && !blocks.contains(&Block::INode) { - blocks.insert(0, Block::INode); - } - - let mut ignore_globs_builder = GlobSetBuilder::new(); - for pattern in ignore_globs_inputs { - let glob = match Glob::new(pattern) { - Ok(g) => g, - Err(e) => { - return Err(Error::with_description( - &e.to_string(), - ErrorKind::ValueValidation, - )); - } - }; - ignore_globs_builder.add(glob); - } - - let ignore_globs = match ignore_globs_builder.build() { - Ok(globs) => globs, - Err(e) => { - return Err(Error::with_description( - &e.to_string(), - ErrorKind::ValueValidation, - )); - } - }; - + /// Set up the `Flags` from either [ArgMatches], a [Config] or its [Default] value. + /// + /// # Errors + /// + /// This can return an [Error], when either the building of the ignore globs or the parsing of + /// the recursion depth parameter fails. + pub fn configure_from(matches: &ArgMatches, config: &Config) -> Result { Ok(Self { - display, - layout, - display_indicators: matches.is_present("indicators"), - recursive, - recursion_depth, - sort_by, - sort_order, - size: SizeFlag::from(size_inputs[size_inputs.len() - 1]), - ignore_globs, - blocks, - // Take only the last value - date: if classic_mode { - DateFlag::Date - } else { - DateFlag::from(date_inputs[date_inputs.len() - 1]) - }, - color: if classic_mode { - WhenFlag::Never - } else { - WhenFlag::from(color_inputs[color_inputs.len() - 1]) - }, - icon: if classic_mode { - WhenFlag::Never - } else { - WhenFlag::from(icon_inputs[icon_inputs.len() - 1]) - }, - icon_theme: IconTheme::from(icon_theme_inputs[icon_theme_inputs.len() - 1]), - directory_order: if classic_mode { - DirOrderFlag::None - } else { - DirOrderFlag::from(dir_order_inputs[dir_order_inputs.len() - 1]) - }, - no_symlink: matches.is_present("no-symlink"), - total_size: matches.is_present("total-size"), - inode, - dereference, + blocks: Blocks::configure_from(matches, config)?, + color: Color::configure_from(matches, config), + date: DateFlag::configure_from(matches, config), + dereference: Dereference::configure_from(matches, config), + display: Display::configure_from(matches, config), + layout: Layout::configure_from(matches, config), + size: SizeFlag::configure_from(matches, config), + display_indicators: Indicators::configure_from(matches, config), + icons: Icons::configure_from(matches, config), + ignore_globs: IgnoreGlobs::configure_from(matches, config)?, + no_symlink: NoSymlink::configure_from(matches, config), + recursion: Recursion::configure_from(matches, config)?, + sorting: Sorting::configure_from(matches, config), + total_size: TotalSize::configure_from(matches, config), }) } } -impl Default for Flags { - fn default() -> Self { - Self { - display: Display::DisplayOnlyVisible, - layout: Layout::Grid, - display_indicators: false, - recursive: false, - recursion_depth: usize::max_value(), - sort_by: SortFlag::Name, - sort_order: SortOrder::Default, - directory_order: DirOrderFlag::None, - size: SizeFlag::Default, - date: DateFlag::Date, - color: WhenFlag::Auto, - icon: WhenFlag::Auto, - icon_theme: IconTheme::Fancy, - blocks: vec![], - no_symlink: false, - total_size: false, - ignore_globs: GlobSet::empty(), - inode: false, - dereference: false, +/// A trait to allow a type to be configured by either command line parameters, a configuration +/// file or a [Default] value. +pub trait Configurable +where + T: std::default::Default, +{ + /// Returns a value from either [ArgMatches], a [Config] or a [Default] value. The first value + /// that is not [None] is used. The order of precedence for the value used is: + /// - [from_arg_matches](Configurable::from_arg_matches) + /// - [from_config](Configurable::from_config) + /// - [Default::default] + /// + /// # Note + /// + /// The configuration file's Yaml is read in any case, to be able to check for errors and print + /// out warnings. + fn configure_from(matches: &ArgMatches, config: &Config) -> T { + let mut result: T = Default::default(); + + if let Some(value) = Self::from_config(config) { + result = value; } - } -} -#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum Block { - Permission, - User, - Group, - Size, - SizeValue, - Date, - Name, - INode, -} -impl<'a> From<&'a str> for Block { - fn from(block: &'a str) -> Self { - match block { - // "filetype" => Block::FileType, - "permission" => Block::Permission, - "user" => Block::User, - "group" => Block::Group, - "size" => Block::Size, - "size_value" => Block::SizeValue, - "date" => Block::Date, - "name" => Block::Name, - "inode" => Block::INode, - _ => panic!("invalid \"time\" flag: {}", block), + if let Some(value) = Self::from_arg_matches(matches) { + result = value; } - } -} -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub enum Display { - DisplayAll, - DisplayAlmostAll, - DisplayDirectoryItself, - DisplayOnlyVisible, -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub enum SizeFlag { - Default, - Short, - Bytes, -} - -impl<'a> From<&'a str> for SizeFlag { - fn from(size: &'a str) -> Self { - match size { - "default" => SizeFlag::Default, - "short" => SizeFlag::Short, - "bytes" => SizeFlag::Bytes, - _ => panic!("invalid \"size\" flag: {}", size), - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum DateFlag { - Date, - Relative, - Formatted(String), -} - -impl<'a> From<&'a str> for DateFlag { - fn from(time: &'a str) -> Self { - match time { - "date" => DateFlag::Date, - "relative" => DateFlag::Relative, - time if time.starts_with('+') => DateFlag::Formatted(time[1..].to_owned()), - _ => panic!("invalid \"time\" flag: {}", time), - } - } -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub enum WhenFlag { - Always, - Auto, - Never, -} -impl<'a> From<&'a str> for WhenFlag { - fn from(when: &'a str) -> Self { - match when { - "always" => WhenFlag::Always, - "auto" => WhenFlag::Auto, - "never" => WhenFlag::Never, - _ => panic!("invalid \"when\" flag: {}", when), - } - } -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub enum SortFlag { - Name, - Time, - Size, - Version, - Extension, -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub enum SortOrder { - Default, - Reverse, -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub enum DirOrderFlag { - None, - First, - Last, -} - -impl<'a> From<&'a str> for DirOrderFlag { - fn from(when: &'a str) -> Self { - match when { - "none" => DirOrderFlag::None, - "first" => DirOrderFlag::First, - "last" => DirOrderFlag::Last, - _ => panic!("invalid \"when\" flag: {}", when), - } - } -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub enum IconTheme { - Unicode, - Fancy, -} - -impl<'a> From<&'a str> for IconTheme { - fn from(theme: &'a str) -> Self { - match theme { - "fancy" => IconTheme::Fancy, - "unicode" => IconTheme::Unicode, - _ => panic!("invalid \"icon-theme\" flag: {}", theme), - } - } -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub enum Layout { - Grid, - Tree, - OneLine, -} - -#[cfg(test)] -mod test { - use super::Flags; - use super::SortFlag; - use crate::app; - use clap::ErrorKind; - - #[test] - fn test_validate_depth_value() { - let matches = app::build() - .get_matches_from_safe(vec!["lsd", "--tree", "--depth", "xx"]) - .unwrap(); - let res = Flags::from_matches(&matches); - - assert!(res.is_err()); - assert_eq!(res.unwrap_err().kind, ErrorKind::ValueValidation); + result } - #[test] - fn test_useless_depth() { - let matches = app::build() - .get_matches_from_safe(vec!["lsd", "--depth", "10"]) - .unwrap(); - let res = Flags::from_matches(&matches); + /// The method to implement the value fetching from command line parameters. + fn from_arg_matches(matches: &ArgMatches) -> Option; - assert!(res.is_err()); - assert_eq!(res.unwrap_err().kind, ErrorKind::MissingRequiredArgument); - } - - #[test] - fn test_duplicate_depth() { - let matches = app::build() - .get_matches_from_safe(vec!["lsd", "--tree", "--depth", "1", "--depth", "2"]) - .unwrap(); - let res = Flags::from_matches(&matches); - - assert!(res.is_ok()); - assert_eq!(res.unwrap().recursion_depth, 2); - } - - #[test] - fn test_missing_depth() { - let matches = app::build() - .get_matches_from_safe(vec!["lsd", "--tree"]) - .unwrap(); - let res = Flags::from_matches(&matches); - - assert!(res.is_ok()); - assert_eq!(res.unwrap().recursion_depth, usize::max_value()); - } - - #[test] - fn test_multi_sort_use_last() { - let matches = app::build() - .get_matches_from_safe(vec!["lsd", "-t", "-S"]) - .unwrap(); - let res = Flags::from_matches(&matches); - - assert!(res.is_ok()); - assert_eq!(res.unwrap().sort_by, SortFlag::Size); - - let matches = app::build() - .get_matches_from_safe(vec!["lsd", "-S", "-t"]) - .unwrap(); - let res = Flags::from_matches(&matches); - - assert!(res.is_ok()); - assert_eq!(res.unwrap().sort_by, SortFlag::Time); - - let matches = app::build() - .get_matches_from_safe(vec!["lsd", "-t", "-S", "-X"]) - .unwrap(); - let res = Flags::from_matches(&matches); - - assert!(res.is_ok()); - assert_eq!(res.unwrap().sort_by, SortFlag::Extension); - } + /// The method to implement the value fetching from a configuration file. This should return + /// [None], if the [Config] does not have a [Yaml]. + fn from_config(config: &Config) -> Option; } diff --git a/src/flags/blocks.rs b/src/flags/blocks.rs new file mode 100644 index 0000000..a0c5434 --- /dev/null +++ b/src/flags/blocks.rs @@ -0,0 +1,538 @@ +//! This module defines the [Blocks] struct. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use its [configure_from](Blocks::configure_from) method. + +use std::convert::TryFrom; + +use crate::config_file::Config; + +use clap::{ArgMatches, Error, ErrorKind}; +use yaml_rust::Yaml; + +/// A struct to hold a [Vec] of [Block]s and to provide methods to create it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Blocks(pub Vec); + +impl Blocks { + /// Returns a value from either [ArgMatches], a [Config] or a default value. + /// Unless the "long" argument is passed, this returns [Default::default]. Otherwise the first + /// value, that is not [None], is used. The order of precedence for the value used is: + /// - [from_arg_matches](Blocks::from_arg_matches) + /// - [from_config](Blocks::from_config) + /// - [long](Blocks::long) + /// + /// No matter if the "long" argument was passed, if the "inode" argument is passed and the + /// `Blocks` does not contain a [Block] of variant [INode](Block::INode) yet, one is prepended + /// to the returned value. + /// + /// # Note + /// + /// The configuration file's Yaml is read in any case, to be able to check for errors and print + /// out warnings. + /// + /// # Errors + /// + /// This errors if any of the [ArgMatches] parameter arguments causes [Block]'s implementation + /// of [TryFrom::try_from] to return an [Err]. + pub fn configure_from(matches: &ArgMatches, config: &Config) -> Result { + let mut result: Result = if matches.is_present("long") { + Ok(Self::long()) + } else { + Ok(Default::default()) + }; + + if matches.is_present("long") { + if config.has_yaml() { + if let Some(value) = Self::from_config(config) { + result = Ok(value); + } + } + + if let Some(value) = Self::from_arg_matches(matches) { + result = value; + } + } + + if matches.is_present("inode") { + if let Ok(blocks) = result.as_mut() { + blocks.optional_prepend_inode(); + } + } + + result + } + + /// Get a potential `Blocks` struct from [ArgMatches]. + /// + /// If the "blocks" argument is passed, then this returns a `Blocks` containing the parameter + /// values in a [Some]. Otherwise if the "long" argument is passed, this returns + /// [Blocks::long]. Finally if none of the previous happened, this returns [None]. + /// + /// # Errors + /// + /// This errors if any of the parameter arguments causes [Block]'s implementation of + /// [TryFrom::try_from] to return an [Err]. + fn from_arg_matches(matches: &ArgMatches) -> Option> { + if matches.occurrences_of("blocks") > 0 { + if let Some(values) = matches.values_of("blocks") { + let mut blocks: Vec = vec![]; + for value in values { + match Block::try_from(value) { + Ok(block) => blocks.push(block), + Err(message) => { + return Some(Err(Error::with_description( + &message, + ErrorKind::ValueValidation, + ))) + } + } + } + Some(Ok(Self(blocks))) + } else { + None + } + } else { + None + } + } + + /// Get a potential `Blocks` struct from a [Config]. + /// + /// If the Config's [Yaml] contains an [Array](Yaml::Array) value pointed to by "blocks", each + /// of its [String](Yaml::String) values is returned in a `Blocks` in a [Some]. Otherwise it + /// returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["blocks"] { + Yaml::BadValue => None, + Yaml::Array(values) => Self::from_yaml_array(values, config), + _ => { + config.print_wrong_type_warning("blocks", "array"); + None + } + } + } else { + None + } + } + + /// Get a [Blocks] from a [Yaml] array. The [Config] is used to log warnings about wrong values + /// in a Yaml. + fn from_yaml_array(values: &[Yaml], config: &Config) -> Option { + let mut blocks: Vec = vec![]; + for array_el in values.iter() { + match array_el { + Yaml::String(value) => match Block::try_from(value.as_str()) { + Ok(block) => blocks.push(block), + Err(err) => config.print_warning(&err), + }, + _ => config.print_warning("The blocks config values have to be strings."), + } + } + if blocks.is_empty() { + None + } else { + Some(Self(blocks)) + } + } + + /// This returns a Blocks struct for the long format. + /// + /// It contains the [Block]s [Permission](Block::Permission), [User](Block::User), + /// [Group](Block::Group), [Size](Block::Size), [Date](Block::Date) and [Name](Block::Name). + fn long() -> Self { + Self(vec![ + Block::Permission, + Block::User, + Block::Group, + Block::Size, + Block::Date, + Block::Name, + ]) + } + + /// Checks whether `self` already contains a [Block] of variant [INode](Block::INode). + fn contains_inode(&self) -> bool { + self.0.contains(&Block::INode) + } + + /// Prepends a [Block] of variant [INode](Block::INode) to `self`. + fn prepend_inode(&mut self) { + self.0.insert(0, Block::INode) + } + + /// Prepends a [Block] of variant [INode](Block::INode), if `self` does not already contain a + /// Block of that variant. + fn optional_prepend_inode(&mut self) { + if !self.contains_inode() { + self.prepend_inode() + } + } +} + +/// The default value for `Blocks` contains a [Vec] of [Name](Block::Name). +impl Default for Blocks { + fn default() -> Self { + Self(vec![Block::Name]) + } +} + +/// A block of data to show. +#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Block { + Permission, + User, + Group, + Size, + SizeValue, + Date, + Name, + INode, +} + +impl TryFrom<&str> for Block { + type Error = String; + + fn try_from(string: &str) -> Result { + match string { + "permission" => Ok(Self::Permission), + "user" => Ok(Self::User), + "group" => Ok(Self::Group), + "size" => Ok(Self::Size), + "size_value" => Ok(Self::SizeValue), + "date" => Ok(Self::Date), + "name" => Ok(Self::Name), + "inode" => Ok(Self::INode), + _ => Err(format!("Not a valid block name: {}", &string)), + } + } +} + +#[cfg(test)] +mod test_blocks { + use super::Block; + use super::Blocks; + + use crate::app; + use crate::config_file::Config; + + use clap::Error; + use yaml_rust::YamlLoader; + + // The following tests are implemented using match expressions instead of the assert_eq macro, + // because clap::Error does not implement PartialEq. + + macro_rules! assert_eq_ok { + ($left:expr, $right:expr) => { + assert!( + match &$left { + Ok(inner) if inner == $right.as_ref().unwrap() => true, + _ => false, + }, + "\nComparison failed:\nWas: {:?}\nShould be: {:?}\n", + &$left, + &$right + ) + }; + } + + #[test] + fn test_configure_from_without_long() { + let argv = vec!["lsd"]; + let target = Ok::<_, Error>(Blocks::default()); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_configure_from_with_long() { + let argv = vec!["lsd", "--long"]; + let target = Ok::<_, Error>(Blocks::long()); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_configure_from_with_blocks_and_without_long() { + let argv = vec!["lsd", "--blocks", "permission"]; + let target = Ok::<_, Error>(Blocks::default()); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_configure_from_with_blocks_and_long() { + let argv = vec!["lsd", "--long", "--blocks", "permission"]; + let target = Ok::<_, Error>(Blocks(vec![Block::Permission])); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_configure_from_with_inode() { + let argv = vec!["lsd", "--inode"]; + + let mut target_blocks = Blocks::default(); + target_blocks.0.insert(0, Block::INode); + let target = Ok::<_, Error>(target_blocks); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_configure_from_prepend_inode_without_long() { + let argv = vec!["lsd", "--blocks", "permission", "--inode"]; + + let mut target_blocks = Blocks::default(); + target_blocks.0.insert(0, Block::INode); + let target = Ok::<_, Error>(target_blocks); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_configure_from_prepend_inode_with_long() { + let argv = vec!["lsd", "--long", "--blocks", "permission", "--inode"]; + let target = Ok::<_, Error>(Blocks(vec![Block::INode, Block::Permission])); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_configure_from_ignore_prepend_inode_without_long() { + let argv = vec!["lsd", "--blocks", "permission,inode", "--inode"]; + + let mut target_blocks = Blocks::default(); + target_blocks.0.insert(0, Block::INode); + let target = Ok::<_, Error>(target_blocks); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_configure_from_ignore_prepend_inode_with_long() { + let argv = vec!["lsd", "--long", "--blocks", "permission,inode", "--inode"]; + let target = Ok::<_, Error>(Blocks(vec![Block::Permission, Block::INode])); + + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let result = Blocks::configure_from(&matches, &Config::with_none()); + + assert_eq_ok!(result, target); + } + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert!(match Blocks::from_arg_matches(&matches) { + None => true, + _ => false, + }); + } + + #[test] + fn test_from_arg_matches_one() { + let argv = vec!["lsd", "--blocks", "permission"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let test_blocks = Blocks(vec![Block::Permission]); + assert!(match Blocks::from_arg_matches(&matches) { + Some(Ok(blocks)) if blocks == test_blocks => true, + _ => false, + }); + } + + #[test] + fn test_from_arg_matches_multi_occurences() { + let argv = vec!["lsd", "--blocks", "permission", "--blocks", "name"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let test_blocks = Blocks(vec![Block::Permission, Block::Name]); + assert!(match Blocks::from_arg_matches(&matches) { + Some(Ok(blocks)) if blocks == test_blocks => true, + _ => false, + }); + } + + #[test] + fn test_from_arg_matches_multi_values() { + let argv = vec!["lsd", "--blocks", "permission,name"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let test_blocks = Blocks(vec![Block::Permission, Block::Name]); + assert!(match Blocks::from_arg_matches(&matches) { + Some(Ok(blocks)) if blocks == test_blocks => true, + _ => false, + }); + } + + #[test] + fn test_from_arg_matches_reversed_default() { + let argv = vec!["lsd", "--blocks", "name,date,size,group,user,permission"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let test_blocks = Blocks(vec![ + Block::Name, + Block::Date, + Block::Size, + Block::Group, + Block::User, + Block::Permission, + ]); + assert!(match Blocks::from_arg_matches(&matches) { + Some(Ok(blocks)) if blocks == test_blocks => true, + _ => false, + }); + } + + #[test] + fn test_from_arg_matches_every_second_one() { + let argv = vec!["lsd", "--blocks", "permission,group,date"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + let test_blocks = Blocks(vec![Block::Permission, Block::Group, Block::Date]); + assert!(match Blocks::from_arg_matches(&matches) { + Some(Ok(blocks)) if blocks == test_blocks => true, + _ => false, + }); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, Blocks::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, Blocks::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_one() { + let yaml_string = "blocks:\n - permission"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + let blocks = Blocks(vec![Block::Permission]); + assert_eq!(Some(blocks), Blocks::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_reversed_default() { + let yaml_string = "blocks: + - name + - date + - size + - group + - user + - permission +"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + let blocks = Blocks(vec![ + Block::Name, + Block::Date, + Block::Size, + Block::Group, + Block::User, + Block::Permission, + ]); + assert_eq!(Some(blocks), Blocks::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_every_second_one() { + let yaml_string = "blocks: + - permission + - group + - date +"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + let blocks = Blocks(vec![Block::Permission, Block::Group, Block::Date]); + assert_eq!(Some(blocks), Blocks::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_invalid_is_ignored() { + let yaml_string = "blocks: + - permission + - foo + - date +"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + let blocks = Blocks(vec![Block::Permission, Block::Date]); + assert_eq!(Some(blocks), Blocks::from_config(&Config::with_yaml(yaml))); + } +} + +#[cfg(test)] +mod test_block { + use super::Block; + + use std::convert::TryFrom; + + #[test] + fn test_err() { + assert_eq!( + Err(String::from("Not a valid block name: foo")), + Block::try_from("foo") + ); + } + + #[test] + fn test_permission() { + assert_eq!(Ok(Block::Permission), Block::try_from("permission")); + } + + #[test] + fn test_user() { + assert_eq!(Ok(Block::User), Block::try_from("user")); + } + + #[test] + fn test_group() { + assert_eq!(Ok(Block::Group), Block::try_from("group")); + } + + #[test] + fn test_size() { + assert_eq!(Ok(Block::Size), Block::try_from("size")); + } + + #[test] + fn test_size_value() { + assert_eq!(Ok(Block::SizeValue), Block::try_from("size_value")); + } + + #[test] + fn test_date() { + assert_eq!(Ok(Block::Date), Block::try_from("date")); + } + + #[test] + fn test_name() { + assert_eq!(Ok(Block::Name), Block::try_from("name")); + } + + #[test] + fn test_inode() { + assert_eq!(Ok(Block::INode), Block::try_from("inode")); + } +} diff --git a/src/flags/color.rs b/src/flags/color.rs new file mode 100644 index 0000000..1dcb096 --- /dev/null +++ b/src/flags/color.rs @@ -0,0 +1,215 @@ +//! This module defines the [Color]. To set it up from [ArgMatches], a [Yaml] and its [Default] +//! value, use its [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// A collection of flags on how to use colors. +#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)] +pub struct Color { + /// When to use color. + pub when: ColorOption, +} + +impl Color { + /// Get a `Color` struct from [ArgMatches], a [Config] or the [Default] values. + /// + /// The [ColorOption] is configured with their respective [Configurable] implementation. + pub fn configure_from(matches: &ArgMatches, config: &Config) -> Self { + let when = ColorOption::configure_from(matches, config); + Self { when } + } +} + +/// The flag showing when to use colors in the output. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum ColorOption { + Always, + Auto, + Never, +} + +impl ColorOption { + /// Get a Color value from a [Yaml] string. The [Config] is used to log warnings about wrong + /// values in a Yaml. + fn from_yaml_string(value: &str, config: &Config) -> Option { + match value { + "always" => Some(Self::Always), + "auto" => Some(Self::Auto), + "never" => Some(Self::Never), + _ => { + config.print_invalid_value_warning("color->when", &value); + None + } + } + } +} + +impl Configurable for ColorOption { + /// Get a potential `ColorOption` variant from [ArgMatches]. + /// + /// If the "classic" argument is passed, then this returns the [ColorOption::Never] variant in + /// a [Some]. Otherwise if the argument is passed, this returns the variant corresponding to + /// its parameter in a [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("classic") { + Some(Self::Never) + } else if matches.occurrences_of("color") > 0 { + match matches.value_of("color") { + Some("always") => Some(Self::Always), + Some("auto") => Some(Self::Auto), + Some("never") => Some(Self::Never), + _ => panic!("This should not be reachable!"), + } + } else { + None + } + } + + /// Get a potential `ColorOption` variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [Boolean](Yaml::Boolean) value pointed to by "classic" + /// and its value is `true`, then this returns the [ColorOption::Never] variant in a [Some]. + /// Otherwise if the Yaml contains a [String](Yaml::String) value pointed to by "color" -> + /// "when" and it is one of "always", "auto" or "never", this returns its corresponding variant + /// in a [Some]. Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + if let Yaml::Boolean(true) = &yaml["classic"] { + Some(Self::Never) + } else { + match &yaml["color"]["when"] { + Yaml::BadValue => None, + Yaml::String(value) => Self::from_yaml_string(&value, &config), + _ => { + config.print_wrong_type_warning("color->when", "string"); + None + } + } + } + } else { + None + } + } +} + +/// The default value for `ColorOption` is [ColorOption::Auto]. +impl Default for ColorOption { + fn default() -> Self { + Self::Auto + } +} + +#[cfg(test)] +mod test_color_option { + use super::ColorOption; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, ColorOption::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_always() { + let argv = vec!["lsd", "--color", "always"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(ColorOption::Always), + ColorOption::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_autp() { + let argv = vec!["lsd", "--color", "auto"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(ColorOption::Auto), + ColorOption::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_never() { + let argv = vec!["lsd", "--color", "never"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(ColorOption::Never), + ColorOption::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_classic_mode() { + let argv = vec!["lsd", "--color", "always", "--classic"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(ColorOption::Never), + ColorOption::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, ColorOption::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, ColorOption::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_always() { + let yaml_string = "color:\n when: always"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(ColorOption::Always), + ColorOption::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_auto() { + let yaml_string = "color:\n when: auto"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(ColorOption::Auto), + ColorOption::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_never() { + let yaml_string = "color:\n when: never"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(ColorOption::Never), + ColorOption::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_classic_mode() { + let yaml_string = "classic: true\ncolor:\n when: always"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(ColorOption::Never), + ColorOption::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/date.rs b/src/flags/date.rs new file mode 100644 index 0000000..b7c77eb --- /dev/null +++ b/src/flags/date.rs @@ -0,0 +1,222 @@ +//! This module defines the [DateFlag]. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use its [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::app; +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// The flag showing which kind of time stamps to display. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum DateFlag { + Date, + Relative, + Formatted(String), +} + +impl DateFlag { + /// Get a value from a date format string. The [Config] is used to log warnings about wrong + /// values in a Yaml. + fn from_format_string(value: &str, config: &Config) -> Option { + match app::validate_time_format(&value) { + Ok(()) => Some(Self::Formatted(value[1..].to_string())), + _ => { + config.print_warning(&format!("Not a valid date format: {}", value)); + None + } + } + } + + /// Get a value from a [Yaml] string. The [Config] is used to log warnings about wrong values + /// in a Yaml. + fn from_yaml_string(value: &str, config: &Config) -> Option { + match value { + "date" => Some(Self::Date), + "relative" => Some(Self::Relative), + _ if value.starts_with('+') => Self::from_format_string(&value, &config), + _ => { + config.print_warning(&format!("Not a valid date value: {}", value)); + None + } + } + } +} + +impl Configurable for DateFlag { + /// Get a potential `DateFlag` variant from [ArgMatches]. + /// + /// If the "classic" argument is passed, then this returns the [DateFlag::Date] variant in a + /// [Some]. Otherwise if the argument is passed, this returns the variant corresponding to its + /// parameter in a [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("classic") { + Some(Self::Date) + } else if matches.occurrences_of("date") > 0 { + match matches.value_of("date") { + Some("date") => Some(Self::Date), + Some("relative") => Some(Self::Relative), + Some(format) if format.starts_with('+') => { + Some(Self::Formatted(format[1..].to_owned())) + } + _ => panic!("This should not be reachable!"), + } + } else { + None + } + } + + /// Get a potential `DateFlag` variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [Boolean](Yaml::Boolean) value pointed to by "classic" + /// and its value is `true`, then this returns the [DateFlag::Date] variant in a [Some]. + /// Otherwise if the Yaml contains a [String](Yaml::String) value pointed to by "date" and it + /// is one of "date" or "relative", this returns its corresponding variant in a [Some]. + /// Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + if let Yaml::Boolean(true) = &yaml["classic"] { + Some(Self::Date) + } else { + match &yaml["date"] { + Yaml::BadValue => None, + Yaml::String(value) => Self::from_yaml_string(&value, &config), + _ => { + config.print_wrong_type_warning("date", "string"); + None + } + } + } + } else { + None + } + } +} + +/// The default value for `DateFlag` is [DateFlag::Date]. +impl Default for DateFlag { + fn default() -> Self { + Self::Date + } +} + +#[cfg(test)] +mod test { + use super::DateFlag; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, DateFlag::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_date() { + let argv = vec!["lsd", "--date", "date"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(DateFlag::Date), DateFlag::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_relative() { + let argv = vec!["lsd", "--date", "relative"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(DateFlag::Relative), + DateFlag::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_format() { + let argv = vec!["lsd", "--date", "+%F"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(DateFlag::Formatted("%F".to_string())), + DateFlag::from_arg_matches(&matches) + ); + } + + #[test] + #[should_panic(expected = "invalid format specifier: %J")] + fn test_from_arg_matches_format_invalid() { + let argv = vec!["lsd", "--date", "+%J"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + DateFlag::from_arg_matches(&matches); + } + + #[test] + fn test_from_arg_matches_classic_mode() { + let argv = vec!["lsd", "--date", "date", "--classic"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(DateFlag::Date), DateFlag::from_arg_matches(&matches)); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, DateFlag::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, DateFlag::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_date() { + let yaml_string = "date: date"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(DateFlag::Date), + DateFlag::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_relative() { + let yaml_string = "date: relative"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(DateFlag::Relative), + DateFlag::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_format() { + let yaml_string = "date: +%F"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(DateFlag::Formatted("%F".to_string())), + DateFlag::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_format_invalid() { + let yaml_string = "date: +%J"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, DateFlag::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_classic_mode() { + let yaml_string = "classic: true\ndate: relative"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(DateFlag::Date), + DateFlag::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/dereference.rs b/src/flags/dereference.rs new file mode 100644 index 0000000..b423f58 --- /dev/null +++ b/src/flags/dereference.rs @@ -0,0 +1,107 @@ +//! This module defines the [Dereference] flag. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use the [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// The flag showing whether to dereference symbolic links. +#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)] +pub struct Dereference(pub bool); + +impl Configurable for Dereference { + /// Get a potential `Dereference` value from [ArgMatches]. + /// + /// If the "dereference" argument is passed, this returns a `Dereference` with value `true` in + /// a [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("dereference") { + Some(Self(true)) + } else { + None + } + } + + /// Get a potential `Dereference` value from a [Config]. + /// + /// If the Config's [Yaml] contains the [Boolean](Yaml::Boolean) value pointed to by + /// "dereference", this returns its value as the value of the `Dereference`, in a [Some]. + /// Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["dereference"] { + Yaml::BadValue => None, + Yaml::Boolean(value) => Some(Self(*value)), + _ => { + config.print_wrong_type_warning("dereference", "boolean"); + None + } + } + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::Dereference; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, Dereference::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_true() { + let argv = vec!["lsd", "--dereference"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(Dereference(true)), + Dereference::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, Dereference::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, Dereference::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_true() { + let yaml_string = "dereference: true"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Dereference(true)), + Dereference::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_false() { + let yaml_string = "dereference: false"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Dereference(false)), + Dereference::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/display.rs b/src/flags/display.rs new file mode 100644 index 0000000..dd4ff05 --- /dev/null +++ b/src/flags/display.rs @@ -0,0 +1,167 @@ +//! This module defines the [Display] flag. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use its [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// The flag showing which file system nodes to display. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum Display { + All, + AlmostAll, + DirectoryItself, + DisplayOnlyVisible, +} + +impl Display { + /// Get a value from a [Yaml] string. The [Config] is used to log warnings about wrong values + /// in a Yaml. + fn from_yaml_string(value: &str, config: &Config) -> Option { + match value { + "all" => Some(Self::All), + "almost-all" => Some(Self::AlmostAll), + "directory-only" => Some(Self::DirectoryItself), + _ => { + config.print_invalid_value_warning("display", &value); + None + } + } + } +} + +impl Configurable for Display { + /// Get a potential `Display` variant from [ArgMatches]. + /// + /// If any of the "all", "almost-all" or "directory-only" arguments is passed, this returns the + /// corresponding `Display` variant in a [Some]. If neither of them is passed, this returns + /// [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("all") { + Some(Self::All) + } else if matches.is_present("almost-all") { + Some(Self::AlmostAll) + } else if matches.is_present("directory-only") { + Some(Self::DirectoryItself) + } else { + None + } + } + + /// Get a potential `Display` variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [String](Yaml::String) value pointed to by "display" and + /// it is either "all", "almost-all" or "directory-only", this returns the corresponding + /// `Display` variant in a [Some]. Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["display"] { + Yaml::BadValue => None, + Yaml::String(value) => Self::from_yaml_string(&value, &config), + _ => { + config.print_wrong_type_warning("display", "string"); + None + } + } + } else { + None + } + } +} + +/// The default value for `Display` is [Display::DisplayOnlyVisible]. +impl Default for Display { + fn default() -> Self { + Self::DisplayOnlyVisible + } +} + +#[cfg(test)] +mod test { + use super::Display; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, Display::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_all() { + let argv = vec!["lsd", "--all"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(Display::All), Display::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_almost_all() { + let argv = vec!["lsd", "--almost-all"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(Display::AlmostAll), + Display::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_directory_only() { + let argv = vec!["lsd", "--directory-only"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(Display::DirectoryItself), + Display::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, Display::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, Display::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_all() { + let yaml_string = "display: all"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Display::All), + Display::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_almost_all() { + let yaml_string = "display: almost-all"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Display::AlmostAll), + Display::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_directory_only() { + let yaml_string = "display: directory-only"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Display::DirectoryItself), + Display::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/icons.rs b/src/flags/icons.rs new file mode 100644 index 0000000..5a02820 --- /dev/null +++ b/src/flags/icons.rs @@ -0,0 +1,356 @@ +//! This module defines the [IconOption]. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use its [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// A collection of flags on how to use icons. +#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)] +pub struct Icons { + /// When to use icons. + pub when: IconOption, + /// Which icon theme to use. + pub theme: IconTheme, +} + +impl Icons { + /// Get an `Icons` struct from [ArgMatches], a [Config] or the [Default] values. + /// + /// The [IconOption] and [IconTheme] are configured with their respective [Configurable] + /// implementation. + pub fn configure_from(matches: &ArgMatches, config: &Config) -> Self { + let when = IconOption::configure_from(matches, config); + let theme = IconTheme::configure_from(matches, config); + Self { when, theme } + } +} + +/// The flag showing when to use icons in the output. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum IconOption { + Always, + Auto, + Never, +} + +impl IconOption { + /// Get a value from a [Yaml] string. The [Config] is used to log warnings about wrong values + /// in a Yaml. + fn from_yaml_string(value: &str, config: &Config) -> Option { + match value { + "always" => Some(Self::Always), + "auto" => Some(Self::Auto), + "never" => Some(Self::Never), + _ => { + config.print_invalid_value_warning("icons->when", &value); + None + } + } + } +} + +impl Configurable for IconOption { + /// Get a potential `IconOption` variant from [ArgMatches]. + /// + /// If the "classic" argument is passed, then this returns the [IconOption::Never] variant in + /// a [Some]. Otherwise if the argument is passed, this returns the variant corresponding to + /// its parameter in a [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("classic") { + Some(Self::Never) + } else if matches.occurrences_of("icon") > 0 { + match matches.value_of("icon") { + Some("always") => Some(Self::Always), + Some("auto") => Some(Self::Auto), + Some("never") => Some(Self::Never), + _ => panic!("This should not be reachable!"), + } + } else { + None + } + } + + /// Get a potential `IconOption` variant from a [Config]. + /// + /// If the Configs' [Yaml] contains a [Boolean](Yaml::Boolean) value pointed to by "classic" + /// and its value is `true`, then this returns the [IconOption::Never] variant in a [Some]. + /// Otherwise if the Yaml contains a [String](Yaml::String) value pointed to by "icons" -> + /// "when" and it is one of "always", "auto" or "never", this returns its corresponding variant + /// in a [Some]. Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + if let Yaml::Boolean(true) = &yaml["classic"] { + Some(Self::Never) + } else { + match &yaml["icons"]["when"] { + Yaml::BadValue => None, + Yaml::String(value) => Self::from_yaml_string(&value, &config), + _ => { + config.print_wrong_type_warning("icons->when", "string"); + None + } + } + } + } else { + None + } + } +} + +/// The default value for the `IconOption` is [IconOption::Auto]. +impl Default for IconOption { + fn default() -> Self { + Self::Auto + } +} + +/// The flag showing which icon theme to use. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum IconTheme { + Unicode, + Fancy, +} + +impl IconTheme { + /// Get a value from a [Yaml] string. The [Config] is used to log warnings about wrong values + /// in a Yaml. + fn from_yaml_string(value: &str, config: &Config) -> Option { + match value { + "fancy" => Some(Self::Fancy), + "unicode" => Some(Self::Unicode), + _ => { + config.print_invalid_value_warning("icons->theme", &value); + None + } + } + } +} + +impl Configurable for IconTheme { + /// Get a potential `IconTheme` variant from [ArgMatches]. + /// + /// If the argument is passed, this returns the variant corresponding to its parameter in a + /// [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.occurrences_of("icon-theme") > 0 { + match matches.value_of("icon-theme") { + Some("fancy") => Some(Self::Fancy), + Some("unicode") => Some(Self::Unicode), + _ => panic!("This should not be reachable!"), + } + } else { + None + } + } + + /// Get a potential `IconTheme` variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [String](Yaml::String) value pointed to by "icons" -> + /// "theme" and it is one of "fancy" or "unicode", this returns its corresponding variant in a + /// [Some]. Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["icons"]["theme"] { + Yaml::BadValue => None, + Yaml::String(value) => Self::from_yaml_string(&value, &config), + _ => { + config.print_wrong_type_warning("icons->theme", "string"); + None + } + } + } else { + None + } + } +} + +/// The default value for `IconTheme` is [IconTheme::Fancy]. +impl Default for IconTheme { + fn default() -> Self { + Self::Fancy + } +} + +#[cfg(test)] +mod test_icon_option { + use super::IconOption; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, IconOption::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_always() { + let argv = vec!["lsd", "--icon", "always"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(IconOption::Always), + IconOption::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_autp() { + let argv = vec!["lsd", "--icon", "auto"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(IconOption::Auto), + IconOption::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_never() { + let argv = vec!["lsd", "--icon", "never"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(IconOption::Never), + IconOption::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_classic_mode() { + let argv = vec!["lsd", "--icon", "always", "--classic"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(IconOption::Never), + IconOption::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, IconOption::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, IconOption::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_always() { + let yaml_string = "icons:\n when: always"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(IconOption::Always), + IconOption::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_auto() { + let yaml_string = "icons:\n when: auto"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(IconOption::Auto), + IconOption::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_never() { + let yaml_string = "icons:\n when: never"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(IconOption::Never), + IconOption::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_classic_mode() { + let yaml_string = "classic: true\nicons:\n when: always"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(IconOption::Never), + IconOption::from_config(&Config::with_yaml(yaml)) + ); + } +} + +#[cfg(test)] +mod test_icon_theme { + use super::IconTheme; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, IconTheme::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_fancy() { + let argv = vec!["lsd", "--icon-theme", "fancy"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(IconTheme::Fancy), + IconTheme::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_unicode() { + let argv = vec!["lsd", "--icon-theme", "unicode"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(IconTheme::Unicode), + IconTheme::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, IconTheme::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, IconTheme::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_fancy() { + let yaml_string = "icons:\n theme: fancy"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(IconTheme::Fancy), + IconTheme::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_unicode() { + let yaml_string = "icons:\n theme: unicode"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(IconTheme::Unicode), + IconTheme::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/ignore_globs.rs b/src/flags/ignore_globs.rs new file mode 100644 index 0000000..710b67c --- /dev/null +++ b/src/flags/ignore_globs.rs @@ -0,0 +1,188 @@ +//! This module defines the [IgnoreGlobs]. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use the [configure_from](IgnoreGlobs::configure_from) method. + +use crate::config_file::Config; + +use clap::{ArgMatches, Error, ErrorKind}; +use globset::{Glob, GlobSet, GlobSetBuilder}; +use yaml_rust::Yaml; + +/// The struct holding a [GlobSet] and methods to build it. +#[derive(Clone, Debug)] +pub struct IgnoreGlobs(pub GlobSet); + +impl IgnoreGlobs { + /// Returns a value from either [ArgMatches], a [Config] or a [Default] value. The first value + /// that is not [None] is used. The order of precedence for the value used is: + /// - [from_arg_matches](IgnoreGlobs::from_arg_matches) + /// - [from_config](IgnoreGlobs::from_config) + /// - [Default::default] + /// + /// # Note + /// + /// The configuration file's Yaml is read in any case, to be able to check for errors and print + /// out warnings. + /// + /// # Errors + /// + /// If either of the [Glob::new] or [GlobSetBuilder.build] methods return an [Err]. + pub fn configure_from(matches: &ArgMatches, config: &Config) -> Result { + let mut result: Result = Ok(Default::default()); + + if config.has_yaml() { + if let Some(value) = Self::from_config(config) { + match value { + Ok(glob_set) => result = Ok(Self(glob_set)), + Err(err) => result = Err(err), + } + } + } + + if let Some(value) = Self::from_arg_matches(matches) { + match value { + Ok(glob_set) => result = Ok(Self(glob_set)), + Err(err) => result = Err(err), + } + } + + result + } + + /// Get a potential [GlobSet] from [ArgMatches]. + /// + /// If the "ignore-glob" argument has been passed, this returns a [Result] in a [Some] with + /// either the built [GlobSet] or an [Error], if any error was encountered while creating the + /// [GlobSet]. If the argument has not been passed, this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option> { + if matches.occurrences_of("ignore-glob") > 0 { + if let Some(values) = matches.values_of("ignore-glob") { + let mut glob_set_builder = GlobSetBuilder::new(); + for value in values { + match Self::create_glob(value) { + Ok(glob) => { + glob_set_builder.add(glob); + } + Err(err) => return Some(Err(err)), + } + } + Some(Self::create_glob_set(&glob_set_builder)) + } else { + None + } + } else { + None + } + } + + /// Get a potential [GlobSet] from a [Config]. + /// + /// If the Config's [Yaml] contains an [Array](Yaml::Array) value pointed to by "ignore-globs", + /// each of its [String](Yaml::String) values is used to build the [GlobSet]. If the building + /// succeeds, the [GlobSet] is returned in the [Result] in a [Some]. If any error is + /// encountered while building, an [Error] is returned in the Result instead. If the Yaml does + /// not contain such a key, this returns [None]. + fn from_config(config: &Config) -> Option> { + if let Some(yaml) = &config.yaml { + match &yaml["ignore-globs"] { + Yaml::BadValue => None, + Yaml::Array(values) => { + let mut glob_set_builder = GlobSetBuilder::new(); + for yaml_str in values.iter() { + if let Yaml::String(value) = yaml_str { + match Self::create_glob(value) { + Ok(glob) => { + glob_set_builder.add(glob); + } + Err(err) => return Some(Err(err)), + } + } + } + Some(Self::create_glob_set(&glob_set_builder)) + } + _ => { + config.print_wrong_type_warning("ignore-globs", "string"); + None + } + } + } else { + None + } + } + + /// Create a [Glob] from a provided pattern. + /// + /// This method is mainly a helper to wrap the handling of potential errors. + fn create_glob(pattern: &str) -> Result { + match Glob::new(pattern) { + Ok(glob) => Ok(glob), + Err(err) => Err(Error::with_description( + &err.to_string(), + ErrorKind::ValueValidation, + )), + } + } + + /// Create a [GlobSet] from a provided [GlobSetBuilder]. + /// + /// This method is mainly a helper to wrap the handling of potential errors. + fn create_glob_set(builder: &GlobSetBuilder) -> Result { + match builder.build() { + Ok(glob_set) => Ok(glob_set), + Err(err) => Err(Error::with_description( + &err.to_string(), + ErrorKind::ValueValidation, + )), + } + } +} + +/// The default value of `IgnoreGlobs` is the empty [GlobSet], returned by [GlobSet::empty()]. +impl Default for IgnoreGlobs { + fn default() -> Self { + Self(GlobSet::empty()) + } +} + +#[cfg(test)] +mod test { + use super::IgnoreGlobs; + + use crate::app; + use crate::config_file::Config; + + use yaml_rust::YamlLoader; + + // The following tests are implemented using match expressions instead of the assert_eq macro, + // because clap::Error does not implement PartialEq. + // + // Further no tests for actually returned GlobSets are implemented, because GlobSet does not + // even implement PartialEq and thus can not be easily compared. + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert!(match IgnoreGlobs::from_arg_matches(&matches) { + None => true, + _ => false, + }); + } + + #[test] + fn test_from_config_none() { + assert!(match IgnoreGlobs::from_config(&Config::with_none()) { + None => true, + _ => false, + }); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert!(match IgnoreGlobs::from_config(&Config::with_yaml(yaml)) { + None => true, + _ => false, + }); + } +} diff --git a/src/flags/indicators.rs b/src/flags/indicators.rs new file mode 100644 index 0000000..8a841b5 --- /dev/null +++ b/src/flags/indicators.rs @@ -0,0 +1,107 @@ +//! This module defines the [Indicators] flag. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use the [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// The flag showing whether to print file type indicators. +#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)] +pub struct Indicators(pub bool); + +impl Configurable for Indicators { + /// Get a potential `Indicators` value from [ArgMatches]. + /// + /// If the "indicators" argument is passed, this returns an `Indicators` with value `true` in a + /// [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("indicators") { + Some(Self(true)) + } else { + None + } + } + + /// Get a potential `Indicators` value from a [Config]. + /// + /// If the Config's [Yaml] contains the [Boolean](Yaml::Boolean) value pointed to by + /// "indicators", this returns its value as the value of the `Indicators`, in a [Some]. + /// Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["indicators"] { + Yaml::BadValue => None, + Yaml::Boolean(value) => Some(Self(*value)), + _ => { + config.print_wrong_type_warning("indicators", "boolean"); + None + } + } + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::Indicators; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, Indicators::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_true() { + let argv = vec!["lsd", "--classify"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(Indicators(true)), + Indicators::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, Indicators::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, Indicators::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_true() { + let yaml_string = "indicators: true"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Indicators(true)), + Indicators::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_false() { + let yaml_string = "indicators: false"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Indicators(false)), + Indicators::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/layout.rs b/src/flags/layout.rs new file mode 100644 index 0000000..450f106 --- /dev/null +++ b/src/flags/layout.rs @@ -0,0 +1,163 @@ +//! This module defines the [Layout] flag. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use its [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// The flag showing which output layout to print. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum Layout { + Grid, + Tree, + OneLine, +} + +impl Configurable for Layout { + /// Get a potential `Layout` variant from [ArgMatches]. + /// + /// If any of the "tree", "long" or "oneline" arguments is passed, this returns the + /// corresponding `Layout` variant in a [Some]. Otherwise if the number of passed "blocks" + /// arguments is greater than 1, this also returns the [OneLine](Layout::OneLine) variant. + /// Finally if neither of them is passed, this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("tree") { + Some(Self::Tree) + } else if matches.is_present("long") + || matches.is_present("oneline") + || matches.is_present("inode") + || matches!(matches.values_of("blocks"), Some(values) if values.len() > 1) + // TODO: handle this differently + { + Some(Self::OneLine) + } else { + None + } + } + + /// Get a potential Layout variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [String](Yaml::String) value pointed to by "layout" and + /// it is either "tree", "oneline" or "grid", this returns the corresponding `Layout` variant + /// in a [Some]. Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["layout"] { + Yaml::BadValue => None, + Yaml::String(value) => match value.as_ref() { + "tree" => Some(Self::Tree), + "oneline" => Some(Self::OneLine), + "grid" => Some(Self::Grid), + _ => { + config.print_invalid_value_warning("layout", &value); + None + } + }, + _ => { + config.print_wrong_type_warning("layout", "string"); + None + } + } + } else { + None + } + } +} + +/// The default value for `Layout` is [Layout::Grid]. +impl Default for Layout { + fn default() -> Self { + Self::Grid + } +} + +#[cfg(test)] +mod test { + use super::Layout; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, Layout::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_tree() { + let argv = vec!["lsd", "--tree"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(Layout::Tree), Layout::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_oneline() { + let argv = vec!["lsd", "--oneline"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(Layout::OneLine), Layout::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_oneline_through_long() { + let argv = vec!["lsd", "--long"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(Layout::OneLine), Layout::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_oneline_through_blocks() { + let argv = vec!["lsd", "--blocks", "permission,name"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(Layout::OneLine), Layout::from_arg_matches(&matches)); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, Layout::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, Layout::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_tree() { + let yaml_string = "layout: tree"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Layout::Tree), + Layout::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_oneline() { + let yaml_string = "layout: oneline"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Layout::OneLine), + Layout::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_grid() { + let yaml_string = "layout: grid"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(Layout::Grid), + Layout::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/recursion.rs b/src/flags/recursion.rs new file mode 100644 index 0000000..96f3085 --- /dev/null +++ b/src/flags/recursion.rs @@ -0,0 +1,337 @@ +//! This module defines the [Recursion] options. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use the [configure_from](Recursion::configure_from) method. + +use crate::config_file::Config; + +use clap::{ArgMatches, Error, ErrorKind}; +use yaml_rust::Yaml; + +/// The options relating to recursion. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub struct Recursion { + /// Whether the recursion into directories is enabled. + pub enabled: bool, + /// The depth for how far to recurse into directories. + pub depth: usize, +} + +impl Recursion { + /// Get the Recursion from either [ArgMatches], a [Config] or the [Default] value. + /// + /// The "enabled" value is determined by [enabled_from](Recursion::enabled_from) and the depth + /// value is determined by [depth_from](Recursion::depth_from). + /// + /// # Errors + /// + /// If [depth_from](Recursion::depth_from) returns an [Error], this returns it. + pub fn configure_from(matches: &ArgMatches, config: &Config) -> Result { + let enabled = Self::enabled_from(matches, config); + let depth = Self::depth_from(matches, config)?; + Ok(Self { enabled, depth }) + } + + /// Get the "enabled" boolean from [ArgMatches], a [Config] or the [Default] value. The first + /// value that is not [None] is used. The order of precedence for the value used is: + /// - [enabled_from_arg_matches](Recursion::enabled_from_arg_matches) + /// - [enabled_from_config](Recursion::enabled_from_config) + /// - [Default::default] + /// + /// # Note + /// + /// The configuration file's Yaml is read in any case, to be able to check for errors and print + /// out warnings. + fn enabled_from(matches: &ArgMatches, config: &Config) -> bool { + let mut result: bool = Default::default(); + + if config.has_yaml() { + if let Some(value) = Self::enabled_from_config(config) { + result = value; + } + } + + if let Some(value) = Self::enabled_from_arg_matches(matches) { + result = value; + } + + result + } + + /// Get a potential "enabled" boolean from [ArgMatches]. + /// + /// If the "recursive" argument is passed, this returns `true` in a [Some]. Otherwise this + /// returns [None]. + fn enabled_from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("recursive") { + Some(true) + } else { + None + } + } + + /// Get a potential "enabled" boolean from a [Config]. + /// + /// If the Config's [Yaml] contains a [Boolean](Yaml::Boolean) value pointed to by "recursion" + /// -> "enabled", this returns its value in a [Some]. Otherwise this returns [None]. + fn enabled_from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["recursion"]["enabled"] { + Yaml::BadValue => None, + Yaml::Boolean(value) => Some(*value), + _ => { + config.print_wrong_type_warning("recursion->enabled", "boolean"); + None + } + } + } else { + None + } + } + + /// Get the "depth" integer from [ArgMatches], a [Config] or the [Default] value. The first + /// value that is not [None] is used. The order of precedence for the value used is: + /// - [depth_from_arg_matches](Recursion::depth_from_arg_matches) + /// - [depth_from_config](Recursion::depth_from_config) + /// - [Default::default] + /// + /// # Note + /// + /// The configuration file's Yaml is read in any case, to be able to check for errors and print + /// out warnings. + /// + /// # Errors + /// + /// If [depth_from_arg_matches](Recursion::depth_from_arg_matches) returns an [Error], this + /// returns it. + fn depth_from(matches: &ArgMatches, config: &Config) -> Result { + let mut result: Result = Ok(usize::max_value()); + + if config.has_yaml() { + if let Some(value) = Self::depth_from_config(config) { + result = Ok(value); + } + } + + if let Some(value) = Self::depth_from_arg_matches(matches) { + result = value; + } + + result + } + + /// Get a potential "depth" value from [ArgMatches]. + /// + /// If the "depth" argument is passed, its parameter is evaluated. If it can be parsed into a + /// [usize], the [Result] is returned in the [Some]. If it can not be parsed an [Error] is + /// returned in the [Some]. If the argument has not been passed, a [None] is returned. + /// + /// # Errors + /// + /// If the parameter to the "depth" argument can not be parsed, this returns an [Error] in a + /// [Some]. + fn depth_from_arg_matches(matches: &ArgMatches) -> Option> { + if let Some(str) = matches.value_of("depth") { + match str.parse::() { + Ok(value) => return Some(Ok(value)), + Err(_) => { + return Some(Err(Error::with_description( + "The argument '--depth' requires a valid positive number.", + ErrorKind::ValueValidation, + ))) + } + } + } + None + } + + /// Get a potential "depth" value from a [Config]. + /// + /// If the Config's [Yaml] contains a positive [Integer](Yaml::Integer) value pointed to by + /// "recursion" -> "depth", this returns its value in a [Some]. Otherwise this returns [None]. + fn depth_from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["recursion"]["depth"] { + Yaml::BadValue => None, + Yaml::Integer(value) => { + if *value > 0 { + Some(*value as usize) + } else { + config.print_warning( + "The recursion->depth value has to be greater than zero.", + ); + None + } + } + _ => { + config.print_wrong_type_warning("recursion->depth", "integer"); + None + } + } + } else { + None + } + } +} + +/// The default values for `Recursion` are the boolean default and [prim@usize::max_value()]. +impl Default for Recursion { + fn default() -> Self { + Self { + depth: usize::max_value(), + enabled: false, + } + } +} + +#[cfg(test)] +mod test { + use super::Recursion; + + use crate::app; + use crate::config_file::Config; + + use clap::ErrorKind; + use yaml_rust::YamlLoader; + + #[test] + fn test_enabled_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, Recursion::enabled_from_arg_matches(&matches)); + } + + #[test] + fn test_enabled_from_arg_matches_true() { + let argv = vec!["lsd", "--recursive"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(true), Recursion::enabled_from_arg_matches(&matches)); + } + + #[test] + fn test_enabled_from_config_none() { + assert_eq!(None, Recursion::enabled_from_config(&Config::with_none())); + } + + #[test] + fn test_enabled_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + None, + Recursion::enabled_from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_enabled_from_config_true() { + let yaml_string = "recursion:\n enabled: true"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(true), + Recursion::enabled_from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_enabled_from_config_false() { + let yaml_string = "recursion:\n enabled: false"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(false), + Recursion::enabled_from_config(&Config::with_yaml(yaml)) + ); + } + + // The following depth_from_arg_matches tests are implemented using match expressions instead + // of the assert_eq macro, because clap::Error does not implement PartialEq. + + #[test] + fn test_depth_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert!(match Recursion::depth_from_arg_matches(&matches) { + None => true, + _ => false, + }); + } + + #[test] + fn test_depth_from_arg_matches_integer() { + let argv = vec!["lsd", "--depth", "42"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert!(match Recursion::depth_from_arg_matches(&matches) { + None => false, + Some(result) => { + match result { + Ok(value) => value == 42, + Err(_) => false, + } + } + }); + } + + #[test] + fn test_depth_from_arg_matches_neg_int() { + let argv = vec!["lsd", "--depth", "\\-42"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert!(match Recursion::depth_from_arg_matches(&matches) { + None => false, + Some(result) => { + match result { + Ok(_) => false, + Err(error) => error.kind == ErrorKind::ValueValidation, + } + } + }); + } + + #[test] + fn test_depth_from_arg_matches_non_int() { + let argv = vec!["lsd", "--depth", "foo"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert!(match Recursion::depth_from_arg_matches(&matches) { + None => false, + Some(result) => { + match result { + Ok(_) => false, + Err(error) => error.kind == ErrorKind::ValueValidation, + } + } + }); + } + + #[test] + fn test_depth_from_config_none() { + assert_eq!(None, Recursion::depth_from_config(&Config::with_none())); + } + + #[test] + fn test_depth_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, Recursion::depth_from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_depth_from_config_pos_integer() { + let yaml_string = "recursion:\n depth: 42"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(42), + Recursion::depth_from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_depth_from_config_neg_integer() { + let yaml_string = "recursion:\n depth: -42"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, Recursion::depth_from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_depth_from_config_string() { + let yaml_string = "recursion:\n depth: foo"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, Recursion::depth_from_config(&Config::with_yaml(yaml))); + } +} diff --git a/src/flags/size.rs b/src/flags/size.rs new file mode 100644 index 0000000..bccfb45 --- /dev/null +++ b/src/flags/size.rs @@ -0,0 +1,159 @@ +//! This module defines the [SizeFlag]. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use its [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// The flag showing which file size units to use. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum SizeFlag { + /// The variant to show file size with SI unit prefix and a B for bytes. + Default, + /// The variant to show file size with only the SI unit prefix. + Short, + /// The variant to show file size in bytes. + Bytes, +} + +impl Configurable for SizeFlag { + /// Get a potential `SizeFlag` variant from [ArgMatches]. + /// + /// If any of the "default", "short" or "bytes" arguments is passed, the corresponding + /// `SizeFlag` variant is returned in a [Some]. If neither of them is passed, this returns + /// [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.occurrences_of("size") > 0 { + match matches.value_of("size") { + Some("default") => Some(Self::Default), + Some("short") => Some(Self::Short), + Some("bytes") => Some(Self::Bytes), + _ => panic!("This should not be reachable!"), + } + } else { + None + } + } + + /// Get a potential `SizeFlag` variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [String](Yaml::String) value, pointed to by "size" and it + /// is either "default", "short" or "bytes", this returns the corresponding `SizeFlag` variant + /// in a [Some]. Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["size"] { + Yaml::BadValue => None, + Yaml::String(value) => match value.as_ref() { + "default" => Some(Self::Default), + "short" => Some(Self::Short), + "bytes" => Some(Self::Bytes), + _ => { + config.print_invalid_value_warning("size", &value); + None + } + }, + _ => { + config.print_wrong_type_warning("size", "string"); + None + } + } + } else { + None + } + } +} + +/// The default value for `SizeFlag` is [SizeFlag::Default]. +impl Default for SizeFlag { + fn default() -> Self { + Self::Default + } +} + +#[cfg(test)] +mod test { + use super::SizeFlag; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, SizeFlag::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_default() { + let argv = vec!["lsd", "--size", "default"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(SizeFlag::Default), + SizeFlag::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_short() { + let args = vec!["lsd", "--size", "short"]; + let matches = app::build().get_matches_from_safe(args).unwrap(); + assert_eq!(Some(SizeFlag::Short), SizeFlag::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_bytes() { + let args = vec!["lsd", "--size", "bytes"]; + let matches = app::build().get_matches_from_safe(args).unwrap(); + assert_eq!(Some(SizeFlag::Bytes), SizeFlag::from_arg_matches(&matches)); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, SizeFlag::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, SizeFlag::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_default() { + let yaml_string = "size: default"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SizeFlag::Default), + SizeFlag::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_short() { + let yaml_string = "size: short"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SizeFlag::Short), + SizeFlag::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_bytes() { + let yaml_string = "size: bytes"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SizeFlag::Bytes), + SizeFlag::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/sorting.rs b/src/flags/sorting.rs new file mode 100644 index 0000000..d1334bf --- /dev/null +++ b/src/flags/sorting.rs @@ -0,0 +1,523 @@ +//! This module defines the [Sorting] options. To set it up from [ArgMatches], a [Yaml] +//! and its [Default] value, use the [configure_from](Sorting::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// A collection of flags on how to sort the output. +#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)] +pub struct Sorting { + pub column: SortColumn, + pub order: SortOrder, + pub dir_grouping: DirGrouping, +} + +impl Sorting { + /// Get a `Sorting` struct from [ArgMatches], a [Config] or the [Default] values. + /// + /// The [SortColumn], [SortOrder] and [DirGrouping] are configured with their respective + /// [Configurable] implementation. + pub fn configure_from(matches: &ArgMatches, config: &Config) -> Self { + let column = SortColumn::configure_from(matches, config); + let order = SortOrder::configure_from(matches, config); + let dir_grouping = DirGrouping::configure_from(matches, config); + Self { + column, + order, + dir_grouping, + } + } +} + +/// The flag showing which column to use for sorting. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum SortColumn { + Extension, + Name, + Time, + Size, + Version, +} + +impl Configurable for SortColumn { + /// Get a potential `SortColumn` variant from [ArgMatches]. + /// + /// If either the "timesort" or "sizesort" arguments are passed, this returns the corresponding + /// `SortColumn` variant in a [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("timesort") { + Some(Self::Time) + } else if matches.is_present("sizesort") { + Some(Self::Size) + } else if matches.is_present("extensionsort") { + Some(Self::Extension) + } else if matches.is_present("versionsort") { + Some(Self::Version) + } else { + None + } + } + + /// Get a potential `SortColumn` variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [String](Yaml::String) value pointed to by "sorting" -> + /// "column" and it is one of "time", "size" or "name", this returns the corresponding variant + /// in a [Some]. Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["sorting"]["column"] { + Yaml::BadValue => None, + Yaml::String(value) => match value.as_ref() { + "extension" => Some(Self::Extension), + "name" => Some(Self::Name), + "time" => Some(Self::Time), + "size" => Some(Self::Size), + "version" => Some(Self::Version), + _ => { + config.print_invalid_value_warning("sorting->column", &value); + None + } + }, + _ => { + config.print_wrong_type_warning("sorting->column", "string"); + None + } + } + } else { + None + } + } +} + +/// The default value for `SortColumn` is [SortColumn::Name]. +impl Default for SortColumn { + fn default() -> Self { + Self::Name + } +} + +/// The flag showing which sort order to use. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum SortOrder { + Default, + Reverse, +} + +impl Configurable for SortOrder { + /// Get a potential `SortOrder` variant from [ArgMatches]. + /// + /// If the "reverse" argument is passed, this returns [SortOrder::Reverse] in a [Some]. + /// Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("reverse") { + Some(Self::Reverse) + } else { + None + } + } + + /// Get a potential `SortOrder` variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [Boolean](Yaml::Boolean) value pointed to by "sorting" -> + /// "reverse", this returns a mapped variant in a [Some]. Otherwise [None] is returned. A + /// `true` maps to [SortOrder::Reverse] and a `false` maps to [SortOrder::Default]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["sorting"]["reverse"] { + Yaml::BadValue => None, + Yaml::Boolean(value) => { + if *value { + Some(Self::Reverse) + } else { + Some(Self::Default) + } + } + _ => { + config.print_wrong_type_warning("sorting->reverse", "boolean"); + None + } + } + } else { + None + } + } +} + +/// The default value for `SortOrder` is [SortOrder::Default]. +impl Default for SortOrder { + fn default() -> Self { + Self::Default + } +} + +/// The flag showing where to place directories. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum DirGrouping { + None, + First, + Last, +} + +impl Configurable for DirGrouping { + /// Get a potential `DirGrouping` variant from [ArgMatches]. + /// + /// If the "classic" argument is passed, then this returns the [DirGrouping::None] variant in a + /// [Some]. Otherwise if the argument is passed, this returns the variant corresponding to its + /// parameter in a [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("classic") { + Some(Self::None) + } else if matches.occurrences_of("group-dirs") > 0 { + match matches.value_of("group-dirs") { + Some("first") => Some(Self::First), + Some("last") => Some(Self::Last), + Some("none") => Some(Self::None), + _ => panic!("This should not be reachable!"), + } + } else { + None + } + } + + /// Get a potential `DirGrouping` variant from a [Config]. + /// + /// If the Config's [Yaml] contains a [Boolean](Yaml::Boolean) value pointed to by "classic" + /// and its value is `true`, then this returns the the [DirGrouping::None] variant in a [Some]. + /// Otherwise if the Yaml contains a [String](Yaml::String) value pointed to by "sorting" -> + /// "dir-grouping" and it is one of "first", "last" or "none", this returns its corresponding + /// variant in a [Some]. Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + if let Yaml::Boolean(true) = &yaml["classic"] { + Some(Self::None) + } else { + match &yaml["sorting"]["dir-grouping"] { + Yaml::BadValue => None, + Yaml::String(value) => match value.as_ref() { + "first" => Some(Self::First), + "last" => Some(Self::Last), + "none" => Some(Self::None), + _ => { + config.print_invalid_value_warning("sorting->dir-grouping", &value); + None + } + }, + _ => { + config.print_wrong_type_warning("sorting->dir-grouping", "string"); + None + } + } + } + } else { + None + } + } +} + +/// The default value for `DirGrouping` is [DirGrouping::None]. +impl Default for DirGrouping { + fn default() -> Self { + Self::None + } +} + +#[cfg(test)] +mod test_sort_column { + use super::SortColumn; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, SortColumn::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_extension() { + let argv = vec!["lsd", "--extensionsort"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(SortColumn::Extension), + SortColumn::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_time() { + let argv = vec!["lsd", "--timesort"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(SortColumn::Time), + SortColumn::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_size() { + let argv = vec!["lsd", "--sizesort"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(SortColumn::Size), + SortColumn::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_version() { + let argv = vec!["lsd", "--versionsort"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(SortColumn::Version), + SortColumn::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, SortColumn::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, SortColumn::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_invalid() { + let yaml_string = "sorting:\n column: foo"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, SortColumn::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_extension() { + let yaml_string = "sorting:\n column: extension"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SortColumn::Extension), + SortColumn::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_name() { + let yaml_string = "sorting:\n column: name"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SortColumn::Name), + SortColumn::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_time() { + let yaml_string = "sorting:\n column: time"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SortColumn::Time), + SortColumn::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_size() { + let yaml_string = "sorting:\n column: size"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SortColumn::Size), + SortColumn::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_version() { + let yaml_string = "sorting:\n column: version"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SortColumn::Version), + SortColumn::from_config(&Config::with_yaml(yaml)) + ); + } +} + +#[cfg(test)] +mod test_sort_order { + use super::SortOrder; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, SortOrder::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_reverse() { + let argv = vec!["lsd", "--reverse"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(SortOrder::Reverse), + SortOrder::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, SortOrder::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, SortOrder::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_default() { + let yaml_string = "sorting:\n reverse: false"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SortOrder::Default), + SortOrder::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_reverse() { + let yaml_string = "sorting:\n reverse: true"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(SortOrder::Reverse), + SortOrder::from_config(&Config::with_yaml(yaml)) + ); + } +} + +#[cfg(test)] +mod test_dir_grouping { + use super::DirGrouping; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, DirGrouping::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_first() { + let argv = vec!["lsd", "--group-dirs", "first"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(DirGrouping::First), + DirGrouping::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_last() { + let argv = vec!["lsd", "--group-dirs", "last"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(DirGrouping::Last), + DirGrouping::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_explicit_none() { + let argv = vec!["lsd", "--group-dirs", "none"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(DirGrouping::None), + DirGrouping::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_arg_matches_classic_mode() { + let argv = vec!["lsd", "--group-dirs", "first", "--classic"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!( + Some(DirGrouping::None), + DirGrouping::from_arg_matches(&matches) + ); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, DirGrouping::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, DirGrouping::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_first() { + let yaml_string = "sorting:\n dir-grouping: first"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(DirGrouping::First), + DirGrouping::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_last() { + let yaml_string = "sorting:\n dir-grouping: last"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(DirGrouping::Last), + DirGrouping::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_explicit_none() { + let yaml_string = "sorting:\n dir-grouping: none"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(DirGrouping::None), + DirGrouping::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_classic_mode() { + let yaml_string = "classic: true\nsorting:\n dir-grouping: first"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(DirGrouping::None), + DirGrouping::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/symlinks.rs b/src/flags/symlinks.rs new file mode 100644 index 0000000..88e56ec --- /dev/null +++ b/src/flags/symlinks.rs @@ -0,0 +1,104 @@ +//! This module defines the [NoSymlink] flag. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use the [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// The flag showing whether to follow symbolic links. +#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)] +pub struct NoSymlink(pub bool); + +impl Configurable for NoSymlink { + /// Get a potential `NoSymlink` value from [ArgMatches]. + /// + /// If the "no-symlink" argument is passed, this returns a `NoSymlink` with value `true` in a + /// [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("no-symlink") { + Some(Self(true)) + } else { + None + } + } + + /// Get a potential `NoSymlink` value from a [Config]. + /// + /// If the Config's [Yaml] contains the [Boolean](Yaml::Boolean) value pointed to by + /// "no-symlink", this returns its value as the value of the `NoSymlink`, in a [Some]. + /// Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["no-symlink"] { + Yaml::BadValue => None, + Yaml::Boolean(value) => Some(Self(*value)), + _ => { + config.print_wrong_type_warning("no-symlink", "boolean"); + None + } + } + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::NoSymlink; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, NoSymlink::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_true() { + let argv = vec!["lsd", "--no-symlink"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(NoSymlink(true)), NoSymlink::from_arg_matches(&matches)); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, NoSymlink::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, NoSymlink::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_true() { + let yaml_string = "no-symlink: true"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(NoSymlink(true)), + NoSymlink::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_false() { + let yaml_string = "no-symlink: false"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(NoSymlink(false)), + NoSymlink::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/flags/total_size.rs b/src/flags/total_size.rs new file mode 100644 index 0000000..4749964 --- /dev/null +++ b/src/flags/total_size.rs @@ -0,0 +1,104 @@ +//! This module defines the [TotalSize] flag. To set it up from [ArgMatches], a [Yaml] and its +//! [Default] value, use the [configure_from](Configurable::configure_from) method. + +use super::Configurable; + +use crate::config_file::Config; + +use clap::ArgMatches; +use yaml_rust::Yaml; + +/// The flag showing whether to show the total size for directories. +#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)] +pub struct TotalSize(pub bool); + +impl Configurable for TotalSize { + /// Get a potential `TotalSize` value from [ArgMatches]. + /// + /// If the "total-size" argument is passed, this returns a `TotalSize` with value `true` in a + /// [Some]. Otherwise this returns [None]. + fn from_arg_matches(matches: &ArgMatches) -> Option { + if matches.is_present("total-size") { + Some(Self(true)) + } else { + None + } + } + + /// Get a potential `TotalSize` value from a [Config]. + /// + /// If the Config's [Yaml] contains the [Boolean](Yaml::Boolean) value pointed to by + /// "total-size", this returns its value as the value of the `TotalSize`, in a [Some]. + /// Otherwise this returns [None]. + fn from_config(config: &Config) -> Option { + if let Some(yaml) = &config.yaml { + match &yaml["total-size"] { + Yaml::BadValue => None, + Yaml::Boolean(value) => Some(Self(*value)), + _ => { + config.print_wrong_type_warning("total-size", "boolean"); + None + } + } + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::TotalSize; + + use crate::app; + use crate::config_file::Config; + use crate::flags::Configurable; + + use yaml_rust::YamlLoader; + + #[test] + fn test_from_arg_matches_none() { + let argv = vec!["lsd"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(None, TotalSize::from_arg_matches(&matches)); + } + + #[test] + fn test_from_arg_matches_true() { + let argv = vec!["lsd", "--total-size"]; + let matches = app::build().get_matches_from_safe(argv).unwrap(); + assert_eq!(Some(TotalSize(true)), TotalSize::from_arg_matches(&matches)); + } + + #[test] + fn test_from_config_none() { + assert_eq!(None, TotalSize::from_config(&Config::with_none())); + } + + #[test] + fn test_from_config_empty() { + let yaml_string = "---"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!(None, TotalSize::from_config(&Config::with_yaml(yaml))); + } + + #[test] + fn test_from_config_true() { + let yaml_string = "total-size: true"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(TotalSize(true)), + TotalSize::from_config(&Config::with_yaml(yaml)) + ); + } + + #[test] + fn test_from_config_false() { + let yaml_string = "total-size: false"; + let yaml = YamlLoader::load_from_str(yaml_string).unwrap()[0].clone(); + assert_eq!( + Some(TotalSize(false)), + TotalSize::from_config(&Config::with_yaml(yaml)) + ); + } +} diff --git a/src/main.rs b/src/main.rs index e1537ce..5e8d53f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ extern crate clap; extern crate ansi_term; extern crate chrono_humanize; +extern crate dirs; extern crate libc; extern crate lscolors; #[cfg(test)] @@ -18,6 +19,8 @@ extern crate terminal_size; extern crate time; extern crate unicode_width; extern crate wild; +extern crate xdg; +extern crate yaml_rust; #[cfg(unix)] extern crate users; @@ -27,6 +30,7 @@ extern crate winapi; mod app; mod color; +mod config_file; mod core; mod display; mod flags; @@ -34,6 +38,7 @@ mod icon; mod meta; mod sort; +use crate::config_file::Config; use crate::core::Core; use crate::flags::Flags; use std::path::PathBuf; @@ -94,7 +99,12 @@ fn main() { .map(PathBuf::from) .collect(); - let flags = Flags::from_matches(&matches).unwrap_or_else(|err| err.exit()); + let config = if matches.is_present("no-config") { + Config::with_none() + } else { + Config::read_config() + }; + let flags = Flags::configure_from(&matches, &config).unwrap_or_else(|err| err.exit()); let core = Core::new(flags); core.run(inputs); diff --git a/src/meta/indicator.rs b/src/meta/indicator.rs index a00346f..845004a 100644 --- a/src/meta/indicator.rs +++ b/src/meta/indicator.rs @@ -23,7 +23,7 @@ impl From for Indicator { impl Indicator { pub fn render(&self, flags: &Flags) -> ColoredString { - if flags.display_indicators { + if flags.display_indicators.0 { ANSIString::from(self.0) } else { ANSIString::from("") @@ -34,13 +34,13 @@ impl Indicator { #[cfg(test)] mod test { use super::Indicator; - use crate::flags::Flags; + use crate::flags::{Flags, Indicators}; use crate::meta::FileType; #[test] fn test_directory_indicator() { let mut flags = Flags::default(); - flags.display_indicators = true; + flags.display_indicators = Indicators(true); let file_type = Indicator::from(FileType::Directory { uid: false }); @@ -50,7 +50,7 @@ mod test { #[test] fn test_executable_file_indicator() { let mut flags = Flags::default(); - flags.display_indicators = true; + flags.display_indicators = Indicators(true); let file_type = Indicator::from(FileType::File { uid: false, @@ -63,7 +63,7 @@ mod test { #[test] fn test_socket_indicator() { let mut flags = Flags::default(); - flags.display_indicators = true; + flags.display_indicators = Indicators(true); let file_type = Indicator::from(FileType::Socket); @@ -73,7 +73,7 @@ mod test { #[test] fn test_symlink_indicator() { let mut flags = Flags::default(); - flags.display_indicators = true; + flags.display_indicators = Indicators(true); let file_type = Indicator::from(FileType::SymLink { is_dir: false }); assert_eq!("@", file_type.render(&flags).to_string().as_str()); @@ -85,7 +85,7 @@ mod test { #[test] fn test_not_represented_indicator() { let mut flags = Flags::default(); - flags.display_indicators = true; + flags.display_indicators = Indicators(true); // The File type doesn't have any indicator let file_type = Indicator::from(FileType::File { diff --git a/src/meta/mod.rs b/src/meta/mod.rs index 4a916f4..f1569fb 100644 --- a/src/meta/mod.rs +++ b/src/meta/mod.rs @@ -20,8 +20,9 @@ pub use self::owner::Owner; pub use self::permissions::Permissions; pub use self::size::Size; pub use self::symlink::SymLink; -pub use crate::flags::{Display, Flags, Layout}; pub use crate::icon::Icons; + +use crate::flags::{Display, Flags, Layout}; use crate::print_error; use std::fs::read_link; @@ -53,7 +54,7 @@ impl Meta { return Ok(None); } - if flags.display == Display::DisplayDirectoryItself { + if flags.display == Display::DirectoryItself { return Ok(None); } @@ -77,14 +78,14 @@ impl Meta { let mut content: Vec = Vec::new(); - if let Display::DisplayAll = flags.display { + if let Display::All = flags.display { let mut current_meta; current_meta = self.clone(); current_meta.name.name = ".".to_owned(); let parent_meta = - Self::from_path(&self.path.join(Component::ParentDir), flags.dereference)?; + Self::from_path(&self.path.join(Component::ParentDir), flags.dereference.0)?; content.push(current_meta); content.push(parent_meta); @@ -97,7 +98,7 @@ impl Meta { .file_name() .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "invalid file name"))?; - if flags.ignore_globs.is_match(&name) { + if flags.ignore_globs.0.is_match(&name) { continue; } @@ -107,7 +108,7 @@ impl Meta { } } - let mut entry_meta = match Self::from_path(&path, flags.dereference) { + let mut entry_meta = match Self::from_path(&path, flags.dereference.0) { Ok(res) => res, Err(err) => { print_error!("lsd: {}: {}\n", path.display(), err); diff --git a/src/sort.rs b/src/sort.rs index ab72929..3e952dc 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -1,4 +1,4 @@ -use crate::flags::{DirOrderFlag, Flags, SortFlag, SortOrder}; +use crate::flags::{DirGrouping, Flags, SortColumn, SortOrder}; use crate::meta::Meta; use human_sort::compare; use std::cmp::Ordering; @@ -7,23 +7,23 @@ pub type SortFn = fn(&Meta, &Meta) -> Ordering; pub fn assemble_sorters(flags: &Flags) -> Vec<(SortOrder, SortFn)> { let mut sorters: Vec<(SortOrder, SortFn)> = vec![]; - match flags.directory_order { - DirOrderFlag::First => { + match flags.sorting.dir_grouping { + DirGrouping::First => { sorters.push((SortOrder::Default, with_dirs_first)); } - DirOrderFlag::Last => { + DirGrouping::Last => { sorters.push((SortOrder::Reverse, with_dirs_first)); } - DirOrderFlag::None => {} + DirGrouping::None => {} }; - let other_sort = match flags.sort_by { - SortFlag::Name => by_name, - SortFlag::Size => by_size, - SortFlag::Time => by_date, - SortFlag::Version => by_version, - SortFlag::Extension => by_extension, + let other_sort = match flags.sorting.column { + SortColumn::Name => by_name, + SortColumn::Size => by_size, + SortColumn::Time => by_date, + SortColumn::Version => by_version, + SortColumn::Extension => by_extension, }; - sorters.push((flags.sort_order, other_sort)); + sorters.push((flags.sorting.order, other_sort)); sorters } @@ -89,14 +89,14 @@ mod tests { let meta_z = Meta::from_path(&path_z, false).expect("failed to get meta"); let mut flags = Flags::default(); - flags.directory_order = DirOrderFlag::First; + flags.sorting.dir_grouping = DirGrouping::First; // Sort with the dirs first let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_z), Ordering::Greater); // Sort with the dirs first (the dirs stay first) - flags.sort_order = SortOrder::Reverse; + flags.sorting.order = SortOrder::Reverse; let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_z), Ordering::Greater); @@ -117,7 +117,7 @@ mod tests { let meta_z = Meta::from_path(&path_z, false).expect("failed to get meta"); let mut flags = Flags::default(); - flags.directory_order = DirOrderFlag::Last; + flags.sorting.dir_grouping = DirGrouping::Last; // Sort with file first let sorter = assemble_sorters(&flags); @@ -143,14 +143,14 @@ mod tests { let meta_z = Meta::from_path(&path_z, false).expect("failed to get meta"); let mut flags = Flags::default(); - flags.directory_order = DirOrderFlag::None; + flags.sorting.dir_grouping = DirGrouping::None; // Sort by name unordered let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_z), Ordering::Less); // Sort by name unordered - flags.sort_order = SortOrder::Reverse; + flags.sorting.order = SortOrder::Reverse; let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_z), Ordering::Greater); @@ -171,14 +171,14 @@ mod tests { let meta_z = Meta::from_path(&path_z, false).expect("failed to get meta"); let mut flags = Flags::default(); - flags.directory_order = DirOrderFlag::None; + flags.sorting.dir_grouping = DirGrouping::None; // Sort by name unordered let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_z), Ordering::Greater); // Sort by name unordered reversed - flags.sort_order = SortOrder::Reverse; + flags.sorting.order = SortOrder::Reverse; let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_z), Ordering::Less); @@ -220,14 +220,14 @@ mod tests { let meta_z = Meta::from_path(&path_z, false).expect("failed to get meta"); let mut flags = Flags::default(); - flags.sort_by = SortFlag::Time; + flags.sorting.column = SortColumn::Time; // Sort by time let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_z), Ordering::Less); // Sort by time reversed - flags.sort_order = SortOrder::Reverse; + flags.sorting.order = SortOrder::Reverse; let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_z), Ordering::Greater); } @@ -257,7 +257,7 @@ mod tests { let meta_t = Meta::from_path(&path_t, false).expect("failed to get meta"); let mut flags = Flags::default(); - flags.sort_by = SortFlag::Extension; + flags.sorting.column = SortColumn::Extension; // Sort by extension let sorter = assemble_sorters(&flags); @@ -287,7 +287,7 @@ mod tests { let meta_c = Meta::from_path(&path_c, false).expect("failed to get meta"); let mut flags = Flags::default(); - flags.sort_by = SortFlag::Version; + flags.sorting.column = SortColumn::Version; let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_b, &meta_a), Ordering::Greater); diff --git a/tests/integration.rs b/tests/integration.rs index 4fb81e8..59083c0 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -17,6 +17,7 @@ fn test_runs_okay() { #[test] fn test_list_empty_directory() { cmd() + .arg("--no-config") .arg(tempdir().path()) .assert() .stdout(predicate::eq("")); @@ -27,12 +28,14 @@ fn test_list_almost_all_empty_directory() { let matched = ""; cmd() .arg("--almost-all") + .arg("--no-config") .arg(tempdir().path()) .assert() .stdout(predicate::eq(matched)); cmd() .arg("-A") + .arg("--no-config") .arg(tempdir().path()) .assert() .stdout(predicate::eq(matched)); @@ -43,12 +46,14 @@ fn test_list_all_empty_directory() { let matched = "\\.\n\\.\\.\n$"; cmd() .arg("--all") + .arg("--no-config") .arg(tempdir().path()) .assert() .stdout(predicate::str::is_match(matched).unwrap()); cmd() .arg("-a") + .arg("--no-config") .arg(tempdir().path()) .assert() .stdout(predicate::str::is_match(matched).unwrap()); @@ -60,6 +65,7 @@ fn test_list_populated_directory() { dir.child("one").touch().unwrap(); dir.child("two").touch().unwrap(); cmd() + .arg("--no-config") .arg(dir.path()) .assert() .stdout(predicate::str::is_match("one\ntwo\n$").unwrap()); @@ -72,6 +78,7 @@ fn test_list_almost_all_populated_directory() { dir.child("two").touch().unwrap(); cmd() .arg("--almost-all") + .arg("--no-config") .arg(dir.path()) .assert() .stdout(predicate::str::is_match("one\ntwo\n$").unwrap()); @@ -84,6 +91,7 @@ fn test_list_all_populated_directory() { dir.child("two").touch().unwrap(); cmd() .arg("--all") + .arg("--no-config") .arg(dir.path()) .assert() .stdout(predicate::str::is_match("\\.\n\\.\\.\none\ntwo\n$").unwrap()); @@ -102,18 +110,40 @@ fn test_list_inode_populated_directory() { cmd() .arg("--inode") + .arg("--no-config") .arg(dir.path()) .assert() .stdout(predicate::str::is_match(matched).unwrap()); cmd() .arg("-i") + .arg("--no-config") .arg(dir.path()) .assert() .stdout(predicate::str::is_match(matched).unwrap()); } #[test] -fn test_list_block_inode_populated_directory() { +fn test_list_block_inode_populated_directory_without_long() { + let dir = tempdir(); + dir.child("one").touch().unwrap(); + dir.child("two").touch().unwrap(); + + #[cfg(unix)] + let matched = "one\ntwo\n$"; + #[cfg(windows)] + let matched = "one\ntwo\n$"; + + cmd() + .arg("--blocks") + .arg("inode,name") + .arg("--no-config") + .arg(dir.path()) + .assert() + .stdout(predicate::str::is_match(matched).unwrap()); +} + +#[test] +fn test_list_block_inode_populated_directory_with_long() { let dir = tempdir(); dir.child("one").touch().unwrap(); dir.child("two").touch().unwrap(); @@ -124,8 +154,10 @@ fn test_list_block_inode_populated_directory() { let matched = "- one\n\\- two\n$"; cmd() + .arg("--long") .arg("--blocks") .arg("inode,name") + .arg("--no-config") .arg(dir.path()) .assert() .stdout(predicate::str::is_match(matched).unwrap()); @@ -134,7 +166,13 @@ fn test_list_block_inode_populated_directory() { #[test] fn test_list_inode_with_long_ok() { let dir = tempdir(); - cmd().arg("-i").arg("-l").arg(dir.path()).assert().success(); + cmd() + .arg("-i") + .arg("-l") + .arg("--no-config") + .arg(dir.path()) + .assert() + .success(); } #[cfg(unix)] @@ -147,11 +185,13 @@ fn test_list_broken_link_ok() { cmd() .arg(&broken_link) + .arg("--no-config") .assert() .stderr(predicate::str::contains(matched).not()); cmd() .arg("-l") + .arg("--no-config") .arg(broken_link) .assert() .stderr(predicate::str::contains(matched).not()); @@ -167,11 +207,13 @@ fn test_nosymlink_on_non_long() { cmd() .arg("-l") + .arg("--no-config") .arg(&link) .assert() .stdout(predicate::str::contains(link_icon)); cmd() + .arg("--no-config") .arg(&link) .assert() .stdout(predicate::str::contains(link_icon).not()); @@ -190,6 +232,7 @@ fn test_dereference_link_right_type_and_no_link() { cmd() .arg("-l") .arg("--dereference") + .arg("--no-config") .arg(&link) .assert() .stdout(predicate::str::starts_with(file_type)) @@ -198,6 +241,7 @@ fn test_dereference_link_right_type_and_no_link() { cmd() .arg("-l") .arg("-L") + .arg("--no-config") .arg(link) .assert() .stdout(predicate::str::starts_with(file_type)) @@ -213,6 +257,7 @@ fn test_show_folder_content_of_symlink() { fs::symlink("target", &link).unwrap(); cmd() + .arg("--no-config") .arg(link) .assert() .stdout(predicate::str::starts_with("link").not()) @@ -229,6 +274,7 @@ fn test_no_show_folder_content_of_symlink_for_long() { cmd() .arg("-l") + .arg("--no-config") .arg(link) .assert() .stdout(predicate::str::starts_with("lrw")) @@ -236,6 +282,7 @@ fn test_no_show_folder_content_of_symlink_for_long() { cmd() .arg("-l") + .arg("--no-config") .arg(dir.path().join("link/")) .assert() .stdout(predicate::str::starts_with(".rw")) @@ -252,6 +299,7 @@ fn test_show_folder_of_symlink_for_long_multi() { cmd() .arg("-l") + .arg("--no-config") .arg(dir.path().join("link/")) .arg(dir.path().join("link")) .assert() @@ -272,9 +320,14 @@ fn test_version_sort() { dir.child("11").touch().unwrap(); dir.child("2").touch().unwrap(); dir.child("22").touch().unwrap(); - cmd().arg("-v").arg(dir.path()).assert().stdout( - predicate::str::is_match("0.2\n0.3.7\n0.11\n0.11.5\n1\n2\n11\n11a\n22\n$").unwrap(), - ); + cmd() + .arg("-v") + .arg("--no-config") + .arg(dir.path()) + .assert() + .stdout( + predicate::str::is_match("0.2\n0.3.7\n0.11\n0.11.5\n1\n2\n11\n11a\n22\n$").unwrap(), + ); } #[test] @@ -285,6 +338,7 @@ fn test_version_sort_overwrite_by_timesort() { cmd() .arg("-v") .arg("-t") + .arg("--no-config") .arg(dir.path()) .assert() .stdout(predicate::str::is_match("11\n2\n$").unwrap()); @@ -302,6 +356,7 @@ fn test_version_sort_overwrite_by_sizesort() { cmd() .arg("-v") .arg("-S") + .arg("--no-config") .arg(dir.path()) .assert() .stdout(predicate::str::is_match("11\n2\n$").unwrap());