jannie

jannie (mirror)
Log | Files | Refs | README | LICENSE

commit 107ffe6c81712bcd71a0a8158924f9d875793429
parent a33457a58df5ef8da428b277742e7306eee9a6ca
Author: Andy Khramtsov <>
Date:   Tue, 14 Jul 2026 01:52:00 +0300

feat: cleanup

Diffstat:
MCargo.lock | 1271++++---------------------------------------------------------------------------
MCargo.toml | 14--------------
Dconfig.yaml | 21---------------------
Dinventory.yaml | 2--
Mreadme.md | 7++-----
Dsrc/args.rs | 10----------
Dsrc/config/logging.rs | 103-------------------------------------------------------------------------------
Dsrc/config/mod.rs | 73-------------------------------------------------------------------------
Dsrc/filetree/mod.rs | 200-------------------------------------------------------------------------------
Dsrc/filetree/node.rs | 76----------------------------------------------------------------------------
Dsrc/inventory.rs | 21---------------------
Dsrc/jannie_new.rs | 1178-------------------------------------------------------------------------------
Msrc/lib.rs | 1126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Dsrc/logging.rs | 86-------------------------------------------------------------------------------
Msrc/main.rs | 2+-
Dsrc/path_processing.rs | 45---------------------------------------------
Dsrc/state.rs | 194-------------------------------------------------------------------------------
17 files changed, 1083 insertions(+), 3346 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -3,951 +3,132 @@ version = 4 [[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - -[[package]] -name = "arraydeque" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "clap" -version = "4.5.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "config" -version = "0.15.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" -dependencies = [ - "async-trait", - "convert_case", - "json5", - "pathdiff", - "ron", - "rust-ini", - "serde-untagged", - "serde_core", - "serde_json", - "toml", - "winnow", - "yaml-rust2", -] - -[[package]] -name = "console" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width", - "windows-sys", -] - -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "deranged" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "dialoguer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" -dependencies = [ - "console", - "shell-words", - "tempfile", - "zeroize", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys", -] - -[[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_filter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "jannie" -version = "1.0.0-dev" -dependencies = [ - "clap", - "config", - "dialoguer", - "indexmap", - "log", - "serde", - "serde_json", - "serde_yaml", - "shellexpand", - "test-log", - "thiserror", - "tokio", - "tracing", - "tracing-appender", - "tracing-subscriber", - "uuid", -] - -[[package]] -name = "js-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.179" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" - -[[package]] -name = "libredox" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" -dependencies = [ - "bitflags", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-multimap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" -dependencies = [ - "dlv-list", - "hashbrown 0.14.5", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "pest" -version = "2.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" -dependencies = [ - "pest", - "sha2", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "proc-macro2" -version = "1.0.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" - -[[package]] -name = "ron" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" -dependencies = [ - "bitflags", - "once_cell", - "serde", - "serde_derive", - "typeid", - "unicode-ident", -] - -[[package]] -name = "rust-ini" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] -name = "serde-untagged" -version = "0.1.9" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "serde_core" -version = "1.0.228" +name = "dirs" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "serde_derive", + "dirs-sys", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "dirs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ - "proc-macro2", - "quote", - "syn", + "libc", + "option-ext", + "redox_users", + "windows-sys", ] [[package]] -name = "serde_json" -version = "1.0.149" +name = "getrandom" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "cfg-if", + "libc", + "wasi", ] [[package]] -name = "serde_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +name = "jannie" +version = "1.0.0-dev" dependencies = [ - "serde_core", + "log", + "shellexpand", ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "libc" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] -name = "sha2" -version = "0.10.9" +name = "libredox" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "cfg-if", - "cpufeatures", - "digest", + "bitflags", + "libc", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "log" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] -name = "shell-words" -version = "1.1.1" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "shellexpand" -version = "3.1.1" +name = "proc-macro2" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ - "dirs", + "unicode-ident", ] [[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.114" +name = "quote" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", - "quote", - "unicode-ident", ] [[package]] -name = "tempfile" -version = "3.24.0" +name = "redox_users" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys", + "getrandom", + "libredox", + "thiserror", ] [[package]] -name = "test-log" -version = "0.2.19" +name = "shellexpand" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" dependencies = [ - "env_logger", - "test-log-macros", - "tracing-subscriber", + "dirs", ] [[package]] -name = "test-log-macros" -version = "0.2.19" +name = "syn" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", - "syn", + "unicode-ident", ] [[package]] @@ -971,312 +152,18 @@ dependencies = [ ] [[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" - -[[package]] -name = "time-macros" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tokio" -version = "1.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" -dependencies = [ - "pin-project-lite", -] - -[[package]] -name = "toml" -version = "0.9.11+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" -dependencies = [ - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_parser" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" -dependencies = [ - "winnow", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-appender" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" -dependencies = [ - "crossbeam-channel", - "thiserror", - "time", - "tracing-subscriber", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" -dependencies = [ - "unicode-ident", -] - -[[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1290,41 +177,3 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "yaml-rust2" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" -dependencies = [ - "arraydeque", - "encoding_rs", - "hashlink", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zmij" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/Cargo.toml b/Cargo.toml @@ -5,19 +5,5 @@ version = "1.0.0-dev" edition = "2024" [dependencies] -clap = { version = "4.5.54", features = ["derive"] } -config = "0.15.19" -dialoguer = "0.12.0" -indexmap = "2.13.0" log = "0.4.33" -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.149" -serde_yaml = "0.9.34" shellexpand = "3.1.1" -test-log = { version = "0.2.19", features = ["trace"] } -thiserror = "2.0.17" -tokio = { version = "1.49.0", features = ["rt-multi-thread", "fs"] } -tracing = "0.1.44" -tracing-appender = "0.2.4" -tracing-subscriber = { version = "0.3.22", features = ["json"] } -uuid = { version = "1.20.0", features = ["v4"] } diff --git a/config.yaml b/config.yaml @@ -1,21 +0,0 @@ -logging: - file: - level: trace - directory: log - prefix: jannie - -root: /home/andy - -filters: - - path: my-space/projects/python - type: whitelist - - path: my-space/projects/python/sound - type: blacklist - - path: my-space/projects/rust - type: whitelist - - path: my-space/projects/openscad - type: whitelist - - path: my-space/projects/rust/jannie - type: blacklist - - path: my-space/projects/rust/jannie/log - type: whitelist diff --git a/inventory.yaml b/inventory.yaml @@ -1,2 +0,0 @@ -items: - - path: "/home/andy/my-space/projects/openscad/cable-holder" diff --git a/readme.md b/readme.md @@ -4,12 +4,9 @@ This is a utility for keeping files on PC organized. In other words, a way to document workspace. -## Why some of the things - -I used `tokio` and `tracing`, because I just did it this way, no particular reason. - It is a thrown-together prototype of a concept I wanted to quickly explore. -Once I use this garbage for a while I will write a better version. +I refactored most of the dependencies out now, only `shellexpand` pulls in some +(about 20). # License diff --git a/src/args.rs b/src/args.rs @@ -1,10 +0,0 @@ -use std::path::PathBuf; - -#[derive(clap::Parser, Debug)] -#[command(version)] -#[command(about = "Enforce clean workspace", long_about = None)] -pub struct Args { - /// Configuration and inventory - #[arg(short, long, default_value = "~/.config/jannie")] - pub dir: PathBuf, -} diff --git a/src/config/logging.rs b/src/config/logging.rs @@ -1,103 +0,0 @@ -use std::path::PathBuf; - -use tracing::Level; - -#[derive(Clone, Debug, Default, serde::Deserialize)] -pub struct LoggingConfig { - #[serde(default)] - pub file: Option<FileConfig>, -} - -#[derive(Clone, Debug, serde::Deserialize)] -pub struct FileConfig { - pub directory: PathBuf, - pub prefix: String, - #[serde(flatten)] - pub subscriber: SubscriberConfig, -} - -#[derive(Clone, Debug, serde::Deserialize)] -pub struct SubscriberConfig { - #[serde(default)] - pub format: LogFormat, - #[serde(default)] - pub level: LogLevel, - #[serde(default)] - pub target_filters: Vec<TargetFilter>, - #[serde(default = "SubscriberConfig::default_ansi")] - pub ansi: bool, - #[serde(default = "SubscriberConfig::default_target")] - pub target: bool, - #[serde(default = "SubscriberConfig::default_file_path")] - pub file_path: bool, - #[serde(default = "SubscriberConfig::default_line_number")] - pub line_number: bool, -} - -impl SubscriberConfig { - pub fn default_ansi() -> bool { - false - } - - pub fn default_target() -> bool { - true - } - - pub fn default_file_path() -> bool { - false - } - - pub fn default_line_number() -> bool { - false - } -} - -#[derive(Clone, Debug, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub struct TargetFilter { - #[serde(default)] - #[serde(rename = "type")] - pub filter_type: TargetFilterType, - pub pattern: String, -} - -#[derive(Clone, Debug, Copy, Default, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum TargetFilterType { - #[default] - Whitelist, - Blacklist, -} - -#[derive(Clone, Debug, Copy, Default, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum LogFormat { - #[default] - Plain, - Json, -} - -#[derive(Clone, Debug, Copy, Default, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum LogLevel { - #[default] - Off, - Error, - Warn, - Info, - Debug, - Trace, -} - -impl From<LogLevel> for Option<Level> { - fn from(value: LogLevel) -> Self { - match value { - LogLevel::Off => None, - LogLevel::Error => Some(Level::ERROR), - LogLevel::Warn => Some(Level::WARN), - LogLevel::Info => Some(Level::INFO), - LogLevel::Debug => Some(Level::DEBUG), - LogLevel::Trace => Some(Level::TRACE), - } - } -} diff --git a/src/config/mod.rs b/src/config/mod.rs @@ -1,73 +0,0 @@ -use std::path::{Path, PathBuf}; - -pub mod logging; - -#[derive(Clone, Debug, serde::Deserialize)] -pub struct Config { - #[serde(default)] - pub logging: logging::LoggingConfig, - pub root: PathBuf, - #[serde(default)] - pub inventory_format: InventoryFormat, - #[serde(default)] - pub filters: Vec<Filter>, -} - -impl Config { - pub fn new(config_path: &Path) -> Result<Self, Error> { - let config = config::Config::builder() - .add_source(config::File::from(config_path)) - .build()?; - Ok(config.try_deserialize()?) - } -} - -#[derive(Clone, Debug, Default, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum InventoryFormat { - #[default] - Yaml, - Json, -} - -#[derive(Clone, Debug, serde::Deserialize)] -pub struct Filter { - pub path: PathBuf, - #[serde(default)] - #[serde(rename = "type")] - pub filter_type: FilterType, -} - -impl Filter { - pub fn is_whitelist(&self) -> bool { - self.filter_type.is_whitelist() - } - - pub fn is_blacklist(&self) -> bool { - self.filter_type.is_blacklist() - } -} - -#[derive(Clone, Debug, Default, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum FilterType { - #[default] - Whitelist, - Blacklist, -} - -impl FilterType { - pub fn is_whitelist(&self) -> bool { - matches!(self, FilterType::Whitelist) - } - - pub fn is_blacklist(&self) -> bool { - matches!(self, FilterType::Blacklist) - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Error building config: {0}")] - Build(#[from] config::ConfigError), -} diff --git a/src/filetree/mod.rs b/src/filetree/mod.rs @@ -1,200 +0,0 @@ -use std::{ - cell::{BorrowError, BorrowMutError, RefCell}, - collections::BTreeMap, - path::{Path, PathBuf}, - rc::Rc, -}; - -use indexmap::IndexMap; -use uuid::Uuid; - -use crate::filetree::node::Node; - -pub mod node; - -pub type NodeRef<M> = Rc<RefCell<Node<M>>>; - -pub struct Filetree<M> { - nodes: IndexMap<Uuid, NodeRef<M>>, - paths: BTreeMap<PathBuf, Uuid>, - root: Uuid, -} - -impl<M> Filetree<M> { - pub fn new(root: Node<M>) -> Self { - // todo: check root better - assert!(root.is_leaf()); - let mut filetree = Filetree { - nodes: IndexMap::new(), - paths: BTreeMap::new(), - root: root.id(), - }; - filetree.insert_node(root, None).unwrap(); - filetree - } - - pub fn root(&self) -> NodeRef<M> { - Rc::clone( - self.nodes - .get(&self.root) - .expect("Filetree should have root"), - ) - } - - pub fn nodes(&self) -> impl Iterator<Item = NodeRef<M>> { - self.nodes.values().map(Rc::clone) - } - - pub fn node(&self, node_id: Uuid) -> Option<NodeRef<M>> { - self.nodes.get(&node_id).map(Rc::clone) - } - - pub fn node_by_path(&self, path: &Path) -> Option<NodeRef<M>> { - self.paths - .get(path) - .map(|node_id| self.nodes.get(node_id).expect("Filetree should have node")) - .map(Rc::clone) - } - - /// Checks if a node with this path exists - pub fn check_path(&self, path: &Path) -> bool { - self.paths.get(path).is_some() - } -} - -impl<M> Filetree<M> { - pub fn insert(&mut self, node: Node<M>) -> Result<(), Error> { - let mut path = node.path().to_owned(); - tracing::debug!("Trying to add node with path {path:?}"); - if path.ends_with("..") { - return Err(Error::ParentDir); - } - if path.ends_with(".") { - return Err(Error::CurrentDir); - } - if self.paths.get(&path).is_some() { - return Err(Error::NodeExists); - } - path.pop(); - tracing::trace!("Searching for node with path {path:?}"); - if let Some(parent) = self.paths.get(&path).copied() { - self.insert_node(node, Some(parent))?; - Ok(()) - } else { - Err(Error::MissingPath) - } - } - - fn insert_node(&mut self, mut node: Node<M>, parent: Option<Uuid>) -> Result<(), Error> { - let node_id = node.id(); - if let Some(parent) = parent { - self.nodes - .get(&parent) - .expect("Should have parent node") - .try_borrow_mut()? - .add_child(node_id); - node.set_parent(parent); - } else { - node.unset_parent(); - } - self.paths.insert(node.path().to_owned(), node_id); - self.nodes.insert(node_id, Rc::new(RefCell::new(node))); - Ok(()) - } -} - -impl<M> Filetree<M> { - pub fn print(&self) -> Result<(), Error> { - let mut print_buffer = Vec::new(); - print_buffer.push((0, self.root)); - while let Some((offset, node_id)) = print_buffer.pop() { - let node = self - .nodes - .get(&node_id) - .expect("Shold have the node") - .try_borrow()?; - println!( - "{}| {}", - " ".repeat(offset), - node.path() - .file_name() - .map(|name| name.to_str()) - .flatten() - .unwrap_or("UNKNOWN") - ); - print_buffer.extend(node.children().map(|child_id| (offset + 1, child_id))); - } - Ok(()) - } -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Error borrowing mutably: {0}")] - BorrowMutError(#[from] BorrowMutError), - #[error("Error borrowing: {0}")] - BorrowError(#[from] BorrowError), - #[error("Node already exists in the tree")] - NodeExists, - #[error("Node extends the path too much, directory does not exist")] - MissingPath, - #[error("Use of current dir is disallowed")] - CurrentDir, - #[error("Use of parent dir is disallowed")] - ParentDir, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test_log::test(test)] - fn cannot_insert_root() { - let root = Node::new("/".into(), ()); - let mut tree = Filetree::new(root); - assert!(dbg!(tree.insert(Node::new("/".into(), ()))).is_err()); - } - - #[test_log::test(test)] - fn cannot_insert_relative() { - let root = Node::new("/".into(), ()); - let mut tree = Filetree::new(root); - assert!(dbg!(tree.insert(Node::new("dir".into(), ()))).is_err()); - } - - #[test_log::test(test)] - fn cannot_insert_dots() { - let root = Node::new("/".into(), ()); - let mut tree = Filetree::new(root); - assert!(dbg!(tree.insert(Node::new("/.".into(), ()))).is_err()); - assert!(dbg!(tree.insert(Node::new("/..".into(), ()))).is_err()); - } - - #[test_log::test(test)] - fn insert_one() { - let root = Node::new("/".into(), ()); - let mut tree = Filetree::new(root); - assert!(dbg!(tree.insert(Node::new("/dir".into(), ()))).is_ok()); - } - - #[test_log::test(test)] - fn insert_exists() { - let root = Node::new("/".into(), ()); - let mut tree = Filetree::new(root); - assert!(dbg!(tree.insert(Node::new("/dir".into(), ()))).is_ok()); - assert!(dbg!(tree.insert(Node::new("/dir".into(), ()))).is_err()); - } - - #[test_log::test(test)] - fn insert_many() { - let root = Node::new("/".into(), ()); - let mut tree = Filetree::new(root); - assert!(dbg!(tree.insert(Node::new("/dir".into(), ()))).is_ok()); - assert!(dbg!(tree.insert(Node::new("/dir/dir/dir".into(), ()))).is_err()); - assert!(dbg!(tree.insert(Node::new("/dir/dir".into(), ()))).is_ok()); - assert!(dbg!(tree.insert(Node::new("/dir/dir".into(), ()))).is_err()); - assert!(dbg!(tree.insert(Node::new("/dir/dir/dir".into(), ()))).is_ok()); - assert!(dbg!(tree.insert(Node::new("/dir2".into(), ()))).is_ok()); - assert!(dbg!(tree.insert(Node::new("/dir3".into(), ()))).is_ok()); - } -} diff --git a/src/filetree/node.rs b/src/filetree/node.rs @@ -1,76 +0,0 @@ -use std::path::{Path, PathBuf}; - -use indexmap::IndexSet; -use uuid::Uuid; - -pub struct Node<M> { - id: Uuid, - path: PathBuf, - meta: M, - children: IndexSet<Uuid>, - parent: Option<Uuid>, -} - -impl<M> Node<M> { - pub fn new(path: PathBuf, meta: M) -> Self { - Self { - id: Uuid::new_v4(), - path, - meta, - children: IndexSet::new(), - parent: None, - } - } - - pub fn id(&self) -> Uuid { - self.id - } - - pub fn path(&self) -> &Path { - &self.path - } - - pub fn meta(&self) -> &M { - &self.meta - } - - pub fn meta_mut(&mut self) -> &mut M { - &mut self.meta - } - - pub fn set_meta(&mut self, meta: M) { - self.meta = meta; - } - - /// A node is a leaf if it has no children - pub fn is_leaf(&self) -> bool { - self.children.is_empty() - } - - pub fn parent(&self) -> Option<Uuid> { - self.parent - } - - pub fn children(&self) -> impl Iterator<Item = Uuid> { - self.children.iter().copied() - } - - pub fn children_vec(&self) -> Vec<Uuid> { - self.children.iter().copied().collect() - } - - pub fn add_child(&mut self, node_id: Uuid) { - self.children.insert(node_id); - } - - pub fn set_parent_opt(&mut self, parent_id: Option<Uuid>) { - self.parent = parent_id; - } - pub fn set_parent(&mut self, parent_id: Uuid) { - self.parent = Some(parent_id); - } - - pub fn unset_parent(&mut self) { - self.parent = None; - } -} diff --git a/src/inventory.rs b/src/inventory.rs @@ -1,21 +0,0 @@ -use std::path::PathBuf; - -#[derive(serde::Deserialize, Debug)] -pub struct Inventory { - pub items: Vec<Item>, -} - -#[derive(serde::Deserialize, Debug)] -pub struct Item { - pub path: PathBuf, - #[serde(default)] - pub check: Check, -} - -#[derive(serde::Deserialize, Clone, Debug, Default)] -#[serde(rename_all = "snake_case")] -pub enum Check { - #[default] - None, - Shell(String), -} diff --git a/src/jannie_new.rs b/src/jannie_new.rs @@ -1,1178 +0,0 @@ -pub mod args { - use std::path::PathBuf; - - #[derive(Debug)] - pub struct Args { - pub config_dir: PathBuf, - } - - pub fn parse_args() -> Result<Args, Error> { - let mut args = std::env::args().skip(1); - let mut config_dir = None; - loop { - let Some(arg) = args.next() else { - break; - }; - match arg.as_str() { - "-d" | "--dir" => { - let Some(value) = args.next() else { - return Err(Error::NotEnoughArguments); - }; - config_dir = Some(value.into()); - } - _ => { - return Err(Error::UnknownArgument); - } - } - } - Ok(Args { - config_dir: config_dir.unwrap_or_else(|| "~/.config/jannie".into()), - }) - } - - #[derive(Debug)] - pub enum Error { - UnknownArgument, - NotEnoughArguments, - } - - impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } - } - - impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::UnknownArgument => write!(f, "Unknown argument"), - Error::NotEnoughArguments => write!(f, "Not enough arguments"), - } - } - } -} - -pub mod config { - use std::path::PathBuf; - - #[derive(Clone, Debug)] - pub struct Config { - pub root: PathBuf, - pub filters: Vec<Filter>, - } - - #[derive(Clone, Debug)] - pub struct Filter { - pub path: PathBuf, - pub filter_type: FilterType, - } - - impl Filter { - pub fn is_whitelist(&self) -> bool { - self.filter_type.is_whitelist() - } - - pub fn is_blacklist(&self) -> bool { - self.filter_type.is_blacklist() - } - } - - #[derive(Clone, Debug, Default)] - pub enum FilterType { - #[default] - Whitelist, - Blacklist, - } - - impl FilterType { - pub fn is_whitelist(&self) -> bool { - matches!(self, FilterType::Whitelist) - } - - pub fn is_blacklist(&self) -> bool { - matches!(self, FilterType::Blacklist) - } - } - - pub fn parse_config(file: &str) -> Result<Config, Error> { - let mut root = None; - let mut filters = Vec::new(); - let mut state = State::Global; - let mut lines = file.lines(); - loop { - let Some(line) = lines.next() else { - break; - }; - let line_trimmed = line.trim(); - if line_trimmed.starts_with("#") { - continue; - } - if line_trimmed.is_empty() { - continue; - } - match state { - State::Global => { - if line_trimmed == "filters:" { - state = State::Filters; - } else { - let Some((key, value)) = line_trimmed.split_once(": ") else { - return Err(Error::InvalidInput); - }; - if key == "root" { - if root.is_some() { - return Err(Error::RootDefinedTwice); - } - root = Some(value.into()); - } else { - return Err(Error::InvalidInput); - } - } - } - State::Filters => { - let Some((key, value)) = line_trimmed.split_once(": ") else { - return Err(Error::InvalidInput); - }; - match key { - "whitelist" => filters.push(Filter { - path: value.into(), - filter_type: FilterType::Whitelist, - }), - "blacklist" => filters.push(Filter { - path: value.into(), - filter_type: FilterType::Blacklist, - }), - _ => return Err(Error::InvalidInput), - }; - } - } - } - Ok(Config { - root: root.unwrap_or_else(|| "~".into()), - filters, - }) - } - - enum State { - Global, - Filters, - } - - #[derive(Debug)] - pub enum Error { - InvalidInput, - RootDefinedTwice, - } - - impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } - } - - impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::InvalidInput => write!(f, "Invalid config input"), - Error::RootDefinedTwice => write!(f, "Root path defined twice"), - } - } - } -} - -pub mod filetree { - pub mod node { - use std::path::{Path, PathBuf}; - /// Allows any additional data to be provided - pub struct Node<M> { - id: usize, - path: PathBuf, - /// Any additional data - meta: M, - children: Vec<usize>, - parent: Option<usize>, - } - - impl<M> Node<M> { - pub fn new(path: PathBuf, id: usize, meta: M) -> Self { - Self { - id, - path, - meta, - children: Vec::new(), - parent: None, - } - } - - pub fn id(&self) -> usize { - self.id - } - - pub fn path(&self) -> &Path { - &self.path - } - - pub fn meta(&self) -> &M { - &self.meta - } - - pub fn meta_mut(&mut self) -> &mut M { - &mut self.meta - } - - pub fn set_meta(&mut self, meta: M) { - self.meta = meta; - } - - /// A node is a leaf if it has no children - pub fn is_leaf(&self) -> bool { - self.children.is_empty() - } - - pub fn parent(&self) -> Option<usize> { - self.parent - } - - pub fn children(&self) -> &[usize] { - &self.children - } - - pub fn add_child(&mut self, node_id: usize) { - self.children.push(node_id); - } - - pub fn set_parent_opt(&mut self, parent_id: Option<usize>) { - self.parent = parent_id; - } - - pub fn set_parent(&mut self, parent_id: usize) { - self.parent = Some(parent_id); - } - - pub fn unset_parent(&mut self) { - self.parent = None; - } - } - } - - use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, - }; - - use node::Node; - - /// The tree currently is insert-only, it's impossible to rearrange it or - /// remove nodes or alter its structure in other ways. - pub struct Filetree<M> { - /// Root is node 0 - nodes: Vec<Node<M>>, - paths: BTreeMap<PathBuf, usize>, - } - - impl<M> Filetree<M> { - pub fn new(root_path: impl Into<PathBuf>, root_meta: M) -> Self { - let root_path = root_path.into(); - let mut paths = BTreeMap::new(); - paths.insert(root_path.clone(), 0); - let root = Node::new(root_path, 0, root_meta); - Filetree { - nodes: vec![root], - paths, - } - } - - pub fn root(&self) -> &Node<M> { - &self.nodes[0] - } - - pub fn nodes(&self) -> &[Node<M>] { - &self.nodes - } - - pub fn get_node(&self, node_id: usize) -> Option<&Node<M>> { - self.nodes.get(node_id) - } - - fn get_node_mut(&mut self, node_id: usize) -> Option<&mut Node<M>> { - self.nodes.get_mut(node_id) - } - - pub fn get_node_by_path(&self, path: &Path) -> Option<&Node<M>> { - self.paths - .get(path) - .copied() - .map(|node_id| &self.nodes[node_id]) - } - - pub fn get_node_id_by_path(&self, path: &Path) -> Option<usize> { - self.paths.get(path).copied() - } - - pub fn get_meta(&self, node_id: usize) -> Option<&M> { - Some(self.get_node(node_id)?.meta()) - } - - pub fn get_meta_mut(&mut self, node_id: usize) -> Option<&mut M> { - Some(self.get_node_mut(node_id)?.meta_mut()) - } - - pub fn get_meta_by_path(&self, path: &Path) -> Option<&M> { - self.get_meta(self.get_node_id_by_path(path)?) - } - - pub fn get_meta_mut_by_path(&mut self, path: &Path) -> Option<&mut M> { - self.get_meta_mut(self.get_node_id_by_path(path)?) - } - - /// Checks if a node with this path exists - pub fn check_path(&self, path: &Path) -> bool { - self.paths.get(path).is_some() - } - - pub fn insert(&mut self, path: impl Into<PathBuf>, meta: M) -> Result<(), Error> { - let path = path.into(); - log::debug!("Trying to add node with path {path:?}"); - if path.ends_with("..") { - return Err(Error::ParentDir); - } - if path.ends_with(".") { - return Err(Error::CurrentDir); - } - if self.check_path(&path) { - return Err(Error::NodeExists); - } - let Some(parent) = path.parent() else { - return Err(Error::MissingPath); - }; - log::trace!("Searching for node with path {parent:?}"); - if let Some(parent) = self.paths.get(parent).copied() { - let id = self.nodes.len(); - let mut node = Node::new(path.clone(), id, meta); - node.set_parent(parent); - self.nodes.push(node); - self.paths.insert(path, id); - self.nodes[parent].add_child(id); - Ok(()) - } else { - Err(Error::MissingPath) - } - } - } - - impl<M> Filetree<M> { - pub fn print(&self) -> Result<(), Error> { - let mut print_buffer = Vec::new(); - print_buffer.push((0, 0)); - while let Some((offset, node_id)) = print_buffer.pop() { - let node = self.nodes.get(node_id).expect("Shold have the node"); - println!( - "{}| {}", - " ".repeat(offset), - node.path() - .file_name() - .map(|name| name.to_str()) - .flatten() - .unwrap_or("UNKNOWN") - ); - print_buffer.extend( - node.children() - .iter() - .copied() - .map(|child_id| (offset + 1, child_id)), - ); - } - Ok(()) - } - } - - #[derive(Debug)] - pub enum Error { - NodeExists, - MissingPath, - CurrentDir, - ParentDir, - } - - impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } - } - - impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::NodeExists => write!(f, "Node with such path already exists"), - Error::MissingPath => write!(f, "Parent path is missing"), - Error::CurrentDir => write!(f, "Used current dir (.) in path"), - Error::ParentDir => write!(f, "Used parent dir (..) in path"), - } - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn cannot_insert_root() { - let mut tree = Filetree::new("/", ()); - assert!(dbg!(tree.insert("/", ())).is_err()); - } - - #[test] - fn cannot_insert_relative() { - let mut tree = Filetree::new("/", ()); - assert!(dbg!(tree.insert("dir", ())).is_err()); - } - - #[test] - fn cannot_insert_dots() { - let mut tree = Filetree::new("/", ()); - assert!(dbg!(tree.insert("/.", ())).is_err()); - assert!(dbg!(tree.insert("/..", ())).is_err()); - } - - #[test] - fn insert_one() { - let mut tree = Filetree::new("/", ()); - assert!(dbg!(tree.insert("/dir", ())).is_ok()); - } - - #[test] - fn insert_exists() { - let mut tree = Filetree::new("/", ()); - assert!(dbg!(tree.insert("/dir", ())).is_ok()); - assert!(dbg!(tree.insert("/dir", ())).is_err()); - } - - #[test] - fn insert_many() { - let mut tree = Filetree::new("/", ()); - assert!(dbg!(tree.insert("/dir", ())).is_ok()); - assert!(dbg!(tree.insert("/dir/dir/dir", ())).is_err()); - assert!(dbg!(tree.insert("/dir/dir", ())).is_ok()); - assert!(dbg!(tree.insert("/dir/dir", ())).is_err()); - assert!(dbg!(tree.insert("/dir/dir/dir", ())).is_ok()); - assert!(dbg!(tree.insert("/dir2", ())).is_ok()); - assert!(dbg!(tree.insert("/dir3", ())).is_ok()); - } - } -} - -pub mod inventory { - use std::path::PathBuf; - - #[derive(Debug)] - pub struct Inventory { - pub items: Vec<Item>, - } - - #[derive(Debug)] - pub struct Item { - pub path: PathBuf, - pub check: Check, - } - - #[derive(Clone, Debug, Default)] - pub enum Check { - #[default] - None, - Shell(String), - } - - pub fn parse_inventory(file: &str) -> Result<Inventory, Error> { - let mut items = Vec::new(); - let mut state = State::Global; - let mut lines = file.lines(); - loop { - let Some(line) = lines.next() else { - match state { - State::Global => break, - State::Item { path, check } => { - items.push(Item { - path, - check: check.unwrap_or_default(), - }); - break; - } - }; - }; - let line_trimmed = line.trim(); - if line_trimmed.starts_with("#") { - continue; - } - if line_trimmed.is_empty() { - continue; - } - match state { - State::Global => { - let Some((key, value)) = line_trimmed.split_once(": ") else { - return Err(Error::InvalidInput); - }; - if key == "path" { - state = State::Item { - path: value.into(), - check: None, - }; - } else { - return Err(Error::InvalidInput); - } - } - State::Item { path, check } => { - let Some((key, value)) = line_trimmed.split_once(": ") else { - return Err(Error::InvalidInput); - }; - match key { - "path" => { - items.push(Item { - path, - check: check.unwrap_or_default(), - }); - state = State::Item { - path: value.into(), - check: None, - }; - } - "check" => { - if check.is_some() { - return Err(Error::CheckDefinedTwice); - } - let check = if value == "none" { - Check::None - } else if let Some(("shell", command)) = value.split_once(" ") { - Check::Shell(command.into()) - } else { - return Err(Error::InvalidInput); - }; - state = State::Item { - path, - check: Some(check), - }; - } - _ => return Err(Error::InvalidInput), - }; - } - } - } - Ok(Inventory { items }) - } - - enum State { - Global, - Item { path: PathBuf, check: Option<Check> }, - } - - #[derive(Debug)] - pub enum Error { - InvalidInput, - CheckDefinedTwice, - } - - impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } - } - - impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::InvalidInput => write!(f, "Invalid input"), - Error::CheckDefinedTwice => write!(f, "Check field defined twice"), - } - } - } -} - -pub mod logging { - //! Logging implementation. - //! Currently only logs to stderr. - //! - //! see [`setup_logging`] - - use log::LevelFilter; - use std::sync::OnceLock; - - static LOGGER: OnceLock<Logger> = OnceLock::new(); - - pub struct Logger { - level: LevelFilter, - } - - impl log::Log for Logger { - fn enabled(&self, metadata: &log::Metadata) -> bool { - metadata.level() <= self.level - } - - fn log(&self, record: &log::Record) { - if self.enabled(record.metadata()) { - eprintln!("[{}]: {}", record.level(), record.args()); - } - } - - fn flush(&self) {} - } - - /// Sets up global logger with a given level. - /// - /// Note: this function may only be called once. - pub fn setup_logging(level: LevelFilter) { - assert!( - LOGGER.set(Logger { level }).is_ok(), - "Should initialize logger" - ); - log::set_logger(LOGGER.get().expect("Logger should be initialized")) - .expect("Should set logger"); - log::set_max_level(level); - } -} - -pub mod path_processing { - use std::{ - env::VarError, - path::{Path, PathBuf}, - }; - - pub fn expand_path(path: &Path) -> Result<PathBuf, Error> { - let path = shellexpand::full( - path.to_str() - .ok_or_else(|| Error::Parse(path.to_path_buf()))?, - ) - .map_err(Error::Expand)?; - Ok(path.as_ref().into()) - } - - pub fn canonicalize_path(path: &Path) -> Result<PathBuf, Error> { - let path = path.canonicalize().map_err(Error::Canonicalize)?; - Ok(path) - } - - /// Expands given path and canonicalizes it - pub fn process_path(path: &Path) -> Result<PathBuf, Error> { - let path = expand_path(path)?; - canonicalize_path(&path) - } - - #[derive(Debug)] - pub enum Error { - Parse(PathBuf), - Expand(shellexpand::LookupError<VarError>), - Canonicalize(std::io::Error), - } - - impl Error { - pub fn is_file_not_found(&self) -> bool { - match *self { - Error::Canonicalize(ref error) => match error.kind() { - std::io::ErrorKind::NotFound => true, - _ => false, - }, - _ => false, - } - } - } - - impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::Parse(_path) => None, - Error::Expand(error) => Some(error), - Error::Canonicalize(error) => Some(error), - } - } - } - - impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::Parse(path) => write!(f, "Error parsing path {path:?}"), - Error::Expand(error) => write!(f, "Error expanding path: {error}"), - Error::Canonicalize(error) => write!(f, "Error canonicalizing path: {error}"), - } - } - } -} - -pub mod state { - use std::path::{Path, PathBuf}; - - use super::config::{Config, Filter, FilterType}; - use super::filetree::{self, Filetree}; - use super::inventory::{self, Inventory}; - use super::path_processing; - - pub struct State { - pub config_dir: PathBuf, - pub config: Config, - pub inventory: Inventory, - /// Paths in filters are absolute and processed - pub filters: Vec<Filter>, - /// Absolute and processed - pub root: PathBuf, - pub blacklist_tree: Filetree<()>, - pub whitelist_tree: Filetree<()>, - pub inventory_tree: Filetree<Option<inventory::Check>>, - } - - impl State { - /// Parses inventory, builds whitelist and blacklist trees, builds an - /// inventory tree, normalizes root path and filters. - pub fn new(config_dir: impl Into<PathBuf>, config: Config) -> Result<Self, Error> { - let config_dir = config_dir.into(); - - let inventory_path = config_dir.join("inventory.txt"); - log::trace!("Reading inventory from {inventory_path:?}"); - let inventory = - std::fs::read_to_string(inventory_path).map_err(Error::ReadInventoryFile)?; - log::trace!("Parsing inventory file contents"); - let inventory = - inventory::parse_inventory(&inventory).map_err(Error::ParseInventory)?; - - log::trace!("Processing root path"); - let root = - path_processing::process_path(&config.root).map_err(Error::ProcessRootPath)?; - - let mut filters = Vec::new(); - - tracing::debug!("Preprocessing filters"); - - for filter in &config.filters { - log::trace!("Preprocessing filter {filter:?}"); - let mut path = config.root.clone(); - path.push(&filter.path); - filters.push(Filter { - path: match path_processing::process_path(&path) { - Ok(path) => path, - Err(error) if error.is_file_not_found() => { - log::warn!("Filter path was not found: {filter:?}"); - continue; - } - Err(error) => Err(Error::ProcessPath(error))?, - }, - filter_type: filter.filter_type.clone(), - }); - } - - log::debug!("Building trees from filters"); - - let mut blacklist_tree = Filetree::new("/", ()); - let mut whitelist_tree = Filetree::new("/", ()); - - for filter in &filters { - let path = &filter.path; - match filter.filter_type { - FilterType::Whitelist => { - log::debug!("Adding {:?} to whitelist", path); - add_to_tree(&mut whitelist_tree, &path)? - } - FilterType::Blacklist => { - if filters - .iter() - .filter(|filter| filter.is_whitelist()) - .find(|filter| &filter.path == path) - .is_some() - { - log::debug!("Skipping {:?} blacklist", path); - } else { - log::debug!("Adding {:?} to blacklist", path); - add_to_tree(&mut blacklist_tree, &path)? - } - } - } - } - - log::debug!("Building inventory tree"); - - let mut inventory_tree = Filetree::new("/", None); - - for item in &inventory.items { - let path = match path_processing::process_path(&item.path) { - Ok(path) => path, - Err(error) if error.is_file_not_found() => { - log::warn!("Inventory item was not found: {item:?}"); - continue; - } - Err(error) => Err(Error::ProcessPath(error))?, - }; - let mut components = path.components(); - let mut path_buf = PathBuf::new(); - loop { - let component = components.next(); - match component { - Some(component) => { - path_buf.push(component); - if inventory_tree.get_node_by_path(&path_buf).is_none() { - inventory_tree - .insert(path_buf.clone(), None) - .map_err(Error::Filetree)?; - } - } - None => { - *inventory_tree - .get_meta_mut_by_path(&path) - .expect("Should have added inventory item") = - Some(item.check.clone()); - break; - } - } - } - } - - Ok(Self { - config_dir: config_dir.into(), - config, - inventory, - filters, - root, - blacklist_tree, - whitelist_tree, - inventory_tree, - }) - } - } - - fn add_to_tree(tree: &mut Filetree<()>, path: &Path) -> Result<(), Error> { - let mut components = path.components(); - let mut path_buf = PathBuf::new(); - loop { - let component = components.next(); - match component { - Some(component) => { - path_buf.push(component); - if tree.get_node_by_path(&path_buf).is_none() { - tree.insert(path_buf.clone(), ()).map_err(Error::Filetree)?; - } - } - None => { - assert!(tree.get_node_by_path(path).is_some()); - break; - } - } - } - Ok(()) - } - - #[derive(Debug)] - pub enum Error { - ReadInventoryFile(std::io::Error), - ProcessPath(path_processing::Error), - ProcessRootPath(path_processing::Error), - ParseInventory(inventory::Error), - Filetree(filetree::Error), - } - - impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::ReadInventoryFile(error) => Some(error), - Error::ProcessPath(error) => Some(error), - Error::ProcessRootPath(error) => Some(error), - Error::ParseInventory(error) => Some(error), - Error::Filetree(error) => Some(error), - } - } - } - - impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::ReadInventoryFile(error) => { - write!(f, "Error reading inventory file: {error}") - } - Error::ProcessPath(error) => write!(f, "Error processing path: {error}"), - Error::ProcessRootPath(error) => write!(f, "Error processing root path: {error}"), - Error::ParseInventory(error) => write!(f, "Error parsing inventory: {error}"), - Error::Filetree(error) => write!(f, "Filetree error: {error}"), - } - } - } -} - -use std::{collections::VecDeque, path::Path}; - -use filetree::Filetree; -use state::State; - -pub fn oneshot() -> Result<(), Box<dyn std::error::Error>> { - let args = args::parse_args().map_err(Error::ParseArgs)?; - let config_dir = - path_processing::process_path(&args.config_dir).map_err(Error::ProcessDirPath)?; - let config_path = config_dir.join("config.txt"); - let config_file = std::fs::read_to_string(&config_path).map_err(Error::ReadConfigFile)?; - let config = config::parse_config(&config_file).map_err(Error::ParseConfig)?; - - logging::setup_logging(log::LevelFilter::Trace); - - log::debug!("Starting with args: {args:#?}"); - log::debug!("Read config: {config:#?}"); - - let state = State::new(&config_dir, config)?; - - log::debug!("Read inventory: {:#?}", state.inventory); - - read(&state)?; - - Ok(()) -} - -/// This meta is used for display tree. -#[derive(Clone, Debug)] -struct Meta { - inventory: Option<Option<String>>, - blacklist: Option<bool>, -} - -/// Scan workspace and display its tree. -fn read(state: &State) -> Result<(), Error> { - let mut filetree = Filetree::new( - "/", - Meta { - blacklist: None, - inventory: None, - }, - ); - - let mut processing_buffer = Vec::new(); - - // Add all whitelist to buffer - for filter in state.filters.iter().filter(|filter| filter.is_whitelist()) { - processing_buffer.push(filter.path.clone()); - } - - // This makes it so every prefix goes first. - processing_buffer.sort(); - - // Has some specific ordering so the tree is built in the correct order. - let mut processing_buffer = VecDeque::from(processing_buffer); - - while let Some(item) = processing_buffer.pop_front() { - tracing::debug!("Adding {:?} to tree", item); - - let allowed_to_scan = allowed_to_scan(state, &item)?; - let inventory_message = (|| -> Result<Option<Option<String>>, Error> { - let Some(node) = state.inventory_tree.get_node_by_path(&item) else { - return Ok(None); - }; - if !node.is_leaf() { - return Ok(None); - } - let Some(check) = node.meta().clone() else { - return Err(Error::NoInventoryCheckInfo); - }; - Ok(Some(match check { - inventory::Check::None => None, - inventory::Check::Shell(cmd) => Some( - String::from_utf8( - std::process::Command::new("sh") - .arg("-c") - .arg(cmd) - .output() - .map_err(Error::Command)? - .stdout, - ) - .map_err(Error::StringUtf8)?, - ), - })) - })()?; - - if let Some(meta) = filetree.get_meta_mut_by_path(&item) { - meta.blacklist = Some(!allowed_to_scan); - } else { - // The earliest prefix that already added - let mut base = item.clone(); - while !filetree.check_path(&base) { - base.pop(); - } - - // What is not yet added - let mut suffix = item - .strip_prefix(&base) - .map_err(Error::BasePrefix)? - .to_owned(); - suffix.pop(); - - for component in suffix.components() { - base.push(component); - filetree - .insert( - base.clone(), - Meta { - blacklist: None, - inventory: None, - }, - ) - .map_err(Error::Filetree)?; - } - filetree - .insert( - item.clone(), - Meta { - blacklist: Some(!allowed_to_scan), - inventory: inventory_message, - }, - ) - .map_err(Error::Filetree)?; - } - - if allowed_to_scan && should_scan(state, &item) { - tracing::debug!("Scanning {:?}", item.file_name()); - let mut iter = std::fs::read_dir(&item).map_err(Error::ReadFs)?; - - let mut dir_buffer = Vec::new(); - - while let Some(result) = iter.next().transpose().map_err(Error::ReadFs)? { - let path = result.path(); - dir_buffer.push(path) - } - - dir_buffer.sort(); - - while let Some(path) = dir_buffer.pop() { - processing_buffer.push_front(path); - } - } - } - - print(&filetree)?; - - Ok(()) -} - -/// Does not check if allowed. -/// Please check if allowed before doing this. -fn should_scan(state: &State, path: &Path) -> bool { - // is allowed - // and is a prefix of a blacklist that is not overridden or is a prefix of an inventory item - let inventory = if let Some(node) = state.inventory_tree.get_node_by_path(path) { - !node.is_leaf() - } else { - false - }; - - state.blacklist_tree.check_path(path) || inventory -} - -/// If a directory is allowed to be scanned. -/// Whitelist has more priority than blacklist on the same level of specificity. -fn allowed_to_scan(state: &State, path: &Path) -> Result<bool, Error> { - // If there is no not-overridden blacklist - - // If no such blacklist filter found that blacklists the path such that - // for this blacklist filter no such whitelist found that whitelists the - // path back aka for which the blacklist is the prefix - Ok(!state - .filters - .iter() - .filter(|filter| filter.is_blacklist()) - .any(|bl_filter| { - path.starts_with(&bl_filter.path) - && !state - .filters - .iter() - .filter(|filter| filter.is_whitelist()) - .any(|wl_filter| { - path.starts_with(&wl_filter.path) - && wl_filter.path.starts_with(&bl_filter.path) - }) - })) -} - -fn print(tree: &Filetree<Meta>) -> Result<(), Error> { - let mut print_buffer = VecDeque::new(); - println!( - "{}", - tree.root().path().to_str().unwrap_or("UNKNOWN").to_owned() - ); - print_buffer.extend( - tree.root() - .children() - .iter() - .copied() - .map(|child_id| (1, child_id)), - ); - while let Some((offset, node_id)) = print_buffer.pop_front() { - let node = tree.get_node(node_id).expect("Shold have the node"); - let name = node - .path() - .file_name() - .map(|name| name.to_str()) - .flatten() - .unwrap_or("UNKNOWN") - .to_owned(); - let offset_text = " ".repeat(offset); - if let Some(msg) = node.meta().inventory.as_ref() { - let msg = msg.as_deref().unwrap_or("\x1b[31mno info\x1b[0m"); - println!( - "{}\x1b[2m|\x1b[0m \x1b[32m{}\x1b[0m \x1b[3m{}\x1b[0m", - offset_text, name, msg, - ); - } else if let Some(true) = node.meta().blacklist { - println!("{}\x1b[2m|\x1b[0m \x1b[2;9m{}\x1b[0m", offset_text, name); - } else if node.is_leaf() { - println!("{}\x1b[2m|\x1b[0m \x1b[31m{}\x1b[0m", offset_text, name); - } else { - println!("{}\x1b[2m|\x1b[0m {}", offset_text, name); - } - for child in node - .children() - .iter() - .copied() - .map(|child_id| (offset + 1, child_id)) - .collect::<Vec<_>>() - .into_iter() - .rev() - { - print_buffer.push_front(child); - } - } - Ok(()) -} - -#[derive(Debug)] -pub enum Error { - ParseArgs(args::Error), - ProcessDirPath(path_processing::Error), - ReadConfigFile(std::io::Error), - ParseConfig(config::Error), - Filetree(filetree::Error), - BasePrefix(std::path::StripPrefixError), - NoInventoryCheckInfo, - Command(std::io::Error), - StringUtf8(std::string::FromUtf8Error), - ReadFs(std::io::Error), -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::ParseArgs(error) => Some(error), - Error::ProcessDirPath(error) => Some(error), - Error::ReadConfigFile(error) => Some(error), - Error::ParseConfig(error) => Some(error), - Error::Filetree(error) => Some(error), - Error::BasePrefix(error) => Some(error), - Error::NoInventoryCheckInfo => None, - Error::Command(error) => Some(error), - Error::StringUtf8(error) => Some(error), - Error::ReadFs(error) => Some(error), - } - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::ParseArgs(error) => write!(f, "Error parsing args: {error}"), - Error::ProcessDirPath(error) => write!(f, "Error processing directory path: {error}"), - Error::ReadConfigFile(error) => write!(f, "Error reading config file: {error}"), - Error::ParseConfig(error) => write!(f, "Error parsing config: {error}"), - Error::Filetree(error) => write!(f, "Filetree error: {error}"), - Error::BasePrefix(error) => write!(f, "Error stripping prefix: {error}"), - Error::NoInventoryCheckInfo => write!(f, "No inventory check info in filetree"), - Error::Command(error) => write!(f, "Error executing command: {error}"), - Error::StringUtf8(error) => write!(f, "Error building utf8 string: {error}"), - Error::ReadFs(error) => write!(f, "Error reading from filesystem: {error}"), - } - } -} diff --git a/src/lib.rs b/src/lib.rs @@ -1,46 +1,939 @@ -use std::{ - cell::{BorrowError, BorrowMutError}, - collections::VecDeque, - path::{Path, StripPrefixError}, - string::FromUtf8Error, -}; +pub mod args { + use std::path::PathBuf; -use clap::Parser; + #[derive(Debug)] + pub struct Args { + pub config_dir: PathBuf, + pub debug: bool, + pub version: bool, + } + + pub fn parse_args() -> Result<Args, Error> { + let mut args = std::env::args().skip(1); + let mut config_dir = None; + let mut debug = None; + let mut version = None; + loop { + let Some(arg) = args.next() else { + break; + }; + match arg.as_str() { + "-d" | "--dir" => { + let Some(value) = args.next() else { + return Err(Error::NotEnoughArguments); + }; + config_dir = Some(value.into()); + } + "--debug" => { + debug = Some(true); + } + "-V" | "--version" => { + version = Some(true); + } + _ => { + return Err(Error::UnknownArgument); + } + } + } + Ok(Args { + config_dir: config_dir.unwrap_or_else(|| "~/.config/jannie".into()), + debug: debug.unwrap_or(false), + version: version.unwrap_or(false), + }) + } + + #[derive(Debug)] + pub enum Error { + UnknownArgument, + NotEnoughArguments, + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::UnknownArgument => write!(f, "Unknown argument"), + Error::NotEnoughArguments => write!(f, "Not enough arguments"), + } + } + } +} + +pub mod config { + use std::path::PathBuf; + + #[derive(Clone, Debug)] + pub struct Config { + pub root: PathBuf, + pub filters: Vec<Filter>, + } + + #[derive(Clone, Debug)] + pub struct Filter { + pub path: PathBuf, + pub filter_type: FilterType, + } + + impl Filter { + pub fn is_whitelist(&self) -> bool { + self.filter_type.is_whitelist() + } + + pub fn is_blacklist(&self) -> bool { + self.filter_type.is_blacklist() + } + } + + #[derive(Clone, Debug, Default)] + pub enum FilterType { + #[default] + Whitelist, + Blacklist, + } + + impl FilterType { + pub fn is_whitelist(&self) -> bool { + matches!(self, FilterType::Whitelist) + } + + pub fn is_blacklist(&self) -> bool { + matches!(self, FilterType::Blacklist) + } + } + + pub fn parse_config(file: &str) -> Result<Config, Error> { + let mut root = None; + let mut filters = Vec::new(); + let mut state = State::Global; + let mut lines = file.lines(); + loop { + let Some(line) = lines.next() else { + break; + }; + let line_trimmed = line.trim(); + if line_trimmed.starts_with("#") { + continue; + } + if line_trimmed.is_empty() { + continue; + } + match state { + State::Global => { + if line_trimmed == "filters:" { + state = State::Filters; + } else { + let Some((key, value)) = line_trimmed.split_once(": ") else { + return Err(Error::InvalidInput); + }; + if key == "root" { + if root.is_some() { + return Err(Error::RootDefinedTwice); + } + root = Some(value.into()); + } else { + return Err(Error::InvalidInput); + } + } + } + State::Filters => { + let Some((key, value)) = line_trimmed.split_once(": ") else { + return Err(Error::InvalidInput); + }; + match key { + "whitelist" => filters.push(Filter { + path: value.into(), + filter_type: FilterType::Whitelist, + }), + "blacklist" => filters.push(Filter { + path: value.into(), + filter_type: FilterType::Blacklist, + }), + _ => return Err(Error::InvalidInput), + }; + } + } + } + Ok(Config { + root: root.unwrap_or_else(|| "~".into()), + filters, + }) + } + + enum State { + Global, + Filters, + } + + #[derive(Debug)] + pub enum Error { + InvalidInput, + RootDefinedTwice, + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidInput => write!(f, "Invalid config input"), + Error::RootDefinedTwice => write!(f, "Root path defined twice"), + } + } + } +} + +pub mod filetree { + pub mod node { + use std::path::{Path, PathBuf}; + /// Allows any additional data to be provided + pub struct Node<M> { + id: usize, + path: PathBuf, + /// Any additional data + meta: M, + children: Vec<usize>, + parent: Option<usize>, + } + + impl<M> Node<M> { + pub fn new(path: PathBuf, id: usize, meta: M) -> Self { + Self { + id, + path, + meta, + children: Vec::new(), + parent: None, + } + } + + pub fn id(&self) -> usize { + self.id + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn meta(&self) -> &M { + &self.meta + } + + pub fn meta_mut(&mut self) -> &mut M { + &mut self.meta + } + + pub fn set_meta(&mut self, meta: M) { + self.meta = meta; + } + + /// A node is a leaf if it has no children + pub fn is_leaf(&self) -> bool { + self.children.is_empty() + } + + pub fn parent(&self) -> Option<usize> { + self.parent + } + + pub fn children(&self) -> &[usize] { + &self.children + } + + pub fn add_child(&mut self, node_id: usize) { + self.children.push(node_id); + } + + pub fn set_parent_opt(&mut self, parent_id: Option<usize>) { + self.parent = parent_id; + } + + pub fn set_parent(&mut self, parent_id: usize) { + self.parent = Some(parent_id); + } + + pub fn unset_parent(&mut self) { + self.parent = None; + } + } + } + + use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + }; + + use node::Node; + + /// The tree currently is insert-only, it's impossible to rearrange it or + /// remove nodes or alter its structure in other ways. + pub struct Filetree<M> { + /// Root is node 0 + nodes: Vec<Node<M>>, + paths: BTreeMap<PathBuf, usize>, + } + + impl<M> Filetree<M> { + pub fn new(root_path: impl Into<PathBuf>, root_meta: M) -> Self { + let root_path = root_path.into(); + let mut paths = BTreeMap::new(); + paths.insert(root_path.clone(), 0); + let root = Node::new(root_path, 0, root_meta); + Filetree { + nodes: vec![root], + paths, + } + } + + pub fn root(&self) -> &Node<M> { + &self.nodes[0] + } + + pub fn nodes(&self) -> &[Node<M>] { + &self.nodes + } + + pub fn get_node(&self, node_id: usize) -> Option<&Node<M>> { + self.nodes.get(node_id) + } + + fn get_node_mut(&mut self, node_id: usize) -> Option<&mut Node<M>> { + self.nodes.get_mut(node_id) + } + + pub fn get_node_by_path(&self, path: &Path) -> Option<&Node<M>> { + self.paths + .get(path) + .copied() + .map(|node_id| &self.nodes[node_id]) + } + + pub fn get_node_id_by_path(&self, path: &Path) -> Option<usize> { + self.paths.get(path).copied() + } + + pub fn get_meta(&self, node_id: usize) -> Option<&M> { + Some(self.get_node(node_id)?.meta()) + } + + pub fn get_meta_mut(&mut self, node_id: usize) -> Option<&mut M> { + Some(self.get_node_mut(node_id)?.meta_mut()) + } + + pub fn get_meta_by_path(&self, path: &Path) -> Option<&M> { + self.get_meta(self.get_node_id_by_path(path)?) + } + + pub fn get_meta_mut_by_path(&mut self, path: &Path) -> Option<&mut M> { + self.get_meta_mut(self.get_node_id_by_path(path)?) + } + + /// Checks if a node with this path exists + pub fn check_path(&self, path: &Path) -> bool { + self.paths.get(path).is_some() + } + + pub fn insert(&mut self, path: impl Into<PathBuf>, meta: M) -> Result<(), Error> { + let path = path.into(); + log::debug!("Trying to add node with path {path:?}"); + if path.ends_with("..") { + return Err(Error::ParentDir); + } + if path.ends_with(".") { + return Err(Error::CurrentDir); + } + if self.check_path(&path) { + return Err(Error::NodeExists); + } + let Some(parent) = path.parent() else { + return Err(Error::MissingPath); + }; + log::trace!("Searching for node with path {parent:?}"); + if let Some(parent) = self.paths.get(parent).copied() { + let id = self.nodes.len(); + let mut node = Node::new(path.clone(), id, meta); + node.set_parent(parent); + self.nodes.push(node); + self.paths.insert(path, id); + self.nodes[parent].add_child(id); + Ok(()) + } else { + Err(Error::MissingPath) + } + } + } + + impl<M> Filetree<M> { + pub fn print(&self) -> Result<(), Error> { + let mut print_buffer = Vec::new(); + print_buffer.push((0, 0)); + while let Some((offset, node_id)) = print_buffer.pop() { + let node = self.nodes.get(node_id).expect("Shold have the node"); + println!( + "{}| {}", + " ".repeat(offset), + node.path() + .file_name() + .map(|name| name.to_str()) + .flatten() + .unwrap_or("UNKNOWN") + ); + print_buffer.extend( + node.children() + .iter() + .copied() + .map(|child_id| (offset + 1, child_id)), + ); + } + Ok(()) + } + } + + #[derive(Debug)] + pub enum Error { + NodeExists, + MissingPath, + CurrentDir, + ParentDir, + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::NodeExists => write!(f, "Node with such path already exists"), + Error::MissingPath => write!(f, "Parent path is missing"), + Error::CurrentDir => write!(f, "Used current dir (.) in path"), + Error::ParentDir => write!(f, "Used parent dir (..) in path"), + } + } + } + + #[cfg(test)] + mod tests { + use super::*; -use crate::{ - args::Args, - config::Config, - filetree::{Filetree, node::Node}, - state::State, -}; + #[test] + fn cannot_insert_root() { + let mut tree = Filetree::new("/", ()); + assert!(dbg!(tree.insert("/", ())).is_err()); + } + + #[test] + fn cannot_insert_relative() { + let mut tree = Filetree::new("/", ()); + assert!(dbg!(tree.insert("dir", ())).is_err()); + } + + #[test] + fn cannot_insert_dots() { + let mut tree = Filetree::new("/", ()); + assert!(dbg!(tree.insert("/.", ())).is_err()); + assert!(dbg!(tree.insert("/..", ())).is_err()); + } + + #[test] + fn insert_one() { + let mut tree = Filetree::new("/", ()); + assert!(dbg!(tree.insert("/dir", ())).is_ok()); + } + + #[test] + fn insert_exists() { + let mut tree = Filetree::new("/", ()); + assert!(dbg!(tree.insert("/dir", ())).is_ok()); + assert!(dbg!(tree.insert("/dir", ())).is_err()); + } + + #[test] + fn insert_many() { + let mut tree = Filetree::new("/", ()); + assert!(dbg!(tree.insert("/dir", ())).is_ok()); + assert!(dbg!(tree.insert("/dir/dir/dir", ())).is_err()); + assert!(dbg!(tree.insert("/dir/dir", ())).is_ok()); + assert!(dbg!(tree.insert("/dir/dir", ())).is_err()); + assert!(dbg!(tree.insert("/dir/dir/dir", ())).is_ok()); + assert!(dbg!(tree.insert("/dir2", ())).is_ok()); + assert!(dbg!(tree.insert("/dir3", ())).is_ok()); + } + } +} + +pub mod inventory { + use std::path::PathBuf; + + #[derive(Debug)] + pub struct Inventory { + pub items: Vec<Item>, + } + + #[derive(Debug)] + pub struct Item { + pub path: PathBuf, + pub check: Check, + } + + #[derive(Clone, Debug, Default)] + pub enum Check { + #[default] + None, + Shell(String), + } + + pub fn parse_inventory(file: &str) -> Result<Inventory, Error> { + let mut items = Vec::new(); + let mut state = State::Global; + let mut lines = file.lines(); + loop { + let Some(line) = lines.next() else { + match state { + State::Global => break, + State::Item { path, check } => { + items.push(Item { + path, + check: check.unwrap_or_default(), + }); + break; + } + }; + }; + let line_trimmed = line.trim(); + if line_trimmed.starts_with("#") { + continue; + } + if line_trimmed.is_empty() { + continue; + } + match state { + State::Global => { + let Some((key, value)) = line_trimmed.split_once(": ") else { + return Err(Error::InvalidInput); + }; + if key == "path" { + state = State::Item { + path: value.into(), + check: None, + }; + } else { + return Err(Error::InvalidInput); + } + } + State::Item { path, check } => { + let Some((key, value)) = line_trimmed.split_once(": ") else { + return Err(Error::InvalidInput); + }; + match key { + "path" => { + items.push(Item { + path, + check: check.unwrap_or_default(), + }); + state = State::Item { + path: value.into(), + check: None, + }; + } + "check" => { + if check.is_some() { + return Err(Error::CheckDefinedTwice); + } + let check = if value == "none" { + Check::None + } else if let Some(("shell", command)) = value.split_once(" ") { + Check::Shell(command.into()) + } else { + return Err(Error::InvalidInput); + }; + state = State::Item { + path, + check: Some(check), + }; + } + _ => return Err(Error::InvalidInput), + }; + } + } + } + Ok(Inventory { items }) + } + + enum State { + Global, + Item { path: PathBuf, check: Option<Check> }, + } + + #[derive(Debug)] + pub enum Error { + InvalidInput, + CheckDefinedTwice, + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidInput => write!(f, "Invalid input"), + Error::CheckDefinedTwice => write!(f, "Check field defined twice"), + } + } + } +} + +pub mod logging { + //! Logging implementation. + //! Currently only logs to stderr. + //! + //! see [`setup_logging`] + + use log::LevelFilter; + use std::sync::OnceLock; + + static LOGGER: OnceLock<Logger> = OnceLock::new(); + + pub struct Logger { + level: LevelFilter, + } + + impl log::Log for Logger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.level + } + + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + eprintln!("[{}]: {}", record.level(), record.args()); + } + } + + fn flush(&self) {} + } + + /// Sets up global logger with a given level. + /// + /// Note: this function may only be called once. + pub fn setup_logging(level: LevelFilter) { + assert!( + LOGGER.set(Logger { level }).is_ok(), + "Should initialize logger" + ); + log::set_logger(LOGGER.get().expect("Logger should be initialized")) + .expect("Should set logger"); + log::set_max_level(level); + } +} + +pub mod path_processing { + use std::{ + env::VarError, + path::{Path, PathBuf}, + }; + + pub fn expand_path(path: &Path) -> Result<PathBuf, Error> { + let path = shellexpand::full( + path.to_str() + .ok_or_else(|| Error::Parse(path.to_path_buf()))?, + ) + .map_err(Error::Expand)?; + Ok(path.as_ref().into()) + } + + pub fn canonicalize_path(path: &Path) -> Result<PathBuf, Error> { + let path = path.canonicalize().map_err(Error::Canonicalize)?; + Ok(path) + } + + /// Expands given path and canonicalizes it + pub fn process_path(path: &Path) -> Result<PathBuf, Error> { + let path = expand_path(path)?; + canonicalize_path(&path) + } + + #[derive(Debug)] + pub enum Error { + Parse(PathBuf), + Expand(shellexpand::LookupError<VarError>), + Canonicalize(std::io::Error), + } + + impl Error { + pub fn is_file_not_found(&self) -> bool { + match *self { + Error::Canonicalize(ref error) => match error.kind() { + std::io::ErrorKind::NotFound => true, + _ => false, + }, + _ => false, + } + } + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Parse(_path) => None, + Error::Expand(error) => Some(error), + Error::Canonicalize(error) => Some(error), + } + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Parse(path) => write!(f, "Error parsing path {path:?}"), + Error::Expand(error) => write!(f, "Error expanding path: {error}"), + Error::Canonicalize(error) => write!(f, "Error canonicalizing path: {error}"), + } + } + } +} + +pub mod state { + use std::path::{Path, PathBuf}; + + use super::config::{Config, Filter, FilterType}; + use super::filetree::{self, Filetree}; + use super::inventory::{self, Inventory}; + use super::path_processing; + + pub struct State { + pub config_dir: PathBuf, + pub config: Config, + pub inventory: Inventory, + /// Paths in filters are absolute and processed + pub filters: Vec<Filter>, + /// Absolute and processed + pub root: PathBuf, + pub blacklist_tree: Filetree<()>, + pub whitelist_tree: Filetree<()>, + pub inventory_tree: Filetree<Option<inventory::Check>>, + } + + impl State { + /// Parses inventory, builds whitelist and blacklist trees, builds an + /// inventory tree, normalizes root path and filters. + pub fn new(config_dir: impl Into<PathBuf>, config: Config) -> Result<Self, Error> { + let config_dir = config_dir.into(); + + let inventory_path = config_dir.join("inventory.txt"); + log::trace!("Reading inventory from {inventory_path:?}"); + let inventory = + std::fs::read_to_string(inventory_path).map_err(Error::ReadInventoryFile)?; + log::trace!("Parsing inventory file contents"); + let inventory = + inventory::parse_inventory(&inventory).map_err(Error::ParseInventory)?; -pub mod args; -pub mod config; -pub mod filetree; -pub mod inventory; -pub mod jannie_new; -pub mod logging; -pub mod path_processing; -pub mod state; + log::trace!("Processing root path"); + let root = + path_processing::process_path(&config.root).map_err(Error::ProcessRootPath)?; -/// `main`, but returns [`Result`]. -pub fn result_main() -> Result<(), Error> { - let args = Args::parse(); - let dir = path_processing::process_path(&args.dir).map_err(Error::ProcessDirPath)?; + let mut filters = Vec::new(); - let config_path = dir.join("config.yaml"); - let config = Config::new(&config_path)?; + log::debug!("Preprocessing filters"); - logging::setup_logging(&config.logging)?; + for filter in &config.filters { + log::trace!("Preprocessing filter {filter:?}"); + let mut path = config.root.clone(); + path.push(&filter.path); + filters.push(Filter { + path: match path_processing::process_path(&path) { + Ok(path) => path, + Err(error) if error.is_file_not_found() => { + log::warn!("Filter path was not found: {filter:?}"); + continue; + } + Err(error) => Err(Error::ProcessPath(error))?, + }, + filter_type: filter.filter_type.clone(), + }); + } + + log::debug!("Building trees from filters"); - tracing::debug!("Starting with args: {args:#?}"); - tracing::debug!("Read config: {config:#?}"); + let mut blacklist_tree = Filetree::new("/", ()); + let mut whitelist_tree = Filetree::new("/", ()); + + for filter in &filters { + let path = &filter.path; + match filter.filter_type { + FilterType::Whitelist => { + log::debug!("Adding {:?} to whitelist", path); + add_to_tree(&mut whitelist_tree, &path)? + } + FilterType::Blacklist => { + if filters + .iter() + .filter(|filter| filter.is_whitelist()) + .find(|filter| &filter.path == path) + .is_some() + { + log::debug!("Skipping {:?} blacklist", path); + } else { + log::debug!("Adding {:?} to blacklist", path); + add_to_tree(&mut blacklist_tree, &path)? + } + } + } + } - let state = State::new(&dir, config)?; + log::debug!("Building inventory tree"); - tracing::debug!("Read inventory: {:#?}", state.inventory); + let mut inventory_tree = Filetree::new("/", None); - state.runtime.block_on(read(&state))?; + for item in &inventory.items { + let path = match path_processing::process_path(&item.path) { + Ok(path) => path, + Err(error) if error.is_file_not_found() => { + log::warn!("Inventory item was not found: {item:?}"); + continue; + } + Err(error) => Err(Error::ProcessPath(error))?, + }; + let mut components = path.components(); + let mut path_buf = PathBuf::new(); + loop { + let component = components.next(); + match component { + Some(component) => { + path_buf.push(component); + if inventory_tree.get_node_by_path(&path_buf).is_none() { + inventory_tree + .insert(path_buf.clone(), None) + .map_err(Error::Filetree)?; + } + } + None => { + *inventory_tree + .get_meta_mut_by_path(&path) + .expect("Should have added inventory item") = + Some(item.check.clone()); + break; + } + } + } + } + + Ok(Self { + config_dir: config_dir.into(), + config, + inventory, + filters, + root, + blacklist_tree, + whitelist_tree, + inventory_tree, + }) + } + } + + fn add_to_tree(tree: &mut Filetree<()>, path: &Path) -> Result<(), Error> { + let mut components = path.components(); + let mut path_buf = PathBuf::new(); + loop { + let component = components.next(); + match component { + Some(component) => { + path_buf.push(component); + if tree.get_node_by_path(&path_buf).is_none() { + tree.insert(path_buf.clone(), ()).map_err(Error::Filetree)?; + } + } + None => { + assert!(tree.get_node_by_path(path).is_some()); + break; + } + } + } + Ok(()) + } + + #[derive(Debug)] + pub enum Error { + ReadInventoryFile(std::io::Error), + ProcessPath(path_processing::Error), + ProcessRootPath(path_processing::Error), + ParseInventory(inventory::Error), + Filetree(filetree::Error), + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::ReadInventoryFile(error) => Some(error), + Error::ProcessPath(error) => Some(error), + Error::ProcessRootPath(error) => Some(error), + Error::ParseInventory(error) => Some(error), + Error::Filetree(error) => Some(error), + } + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::ReadInventoryFile(error) => { + write!(f, "Error reading inventory file: {error}") + } + Error::ProcessPath(error) => write!(f, "Error processing path: {error}"), + Error::ProcessRootPath(error) => write!(f, "Error processing root path: {error}"), + Error::ParseInventory(error) => write!(f, "Error parsing inventory: {error}"), + Error::Filetree(error) => write!(f, "Filetree error: {error}"), + } + } + } +} + +use std::{collections::VecDeque, path::Path}; + +use filetree::Filetree; +use state::State; + +pub fn oneshot() -> Result<(), Box<dyn std::error::Error>> { + let args = args::parse_args().map_err(Error::ParseArgs)?; + + if args.version { + println!("jannie {}", env!("CARGO_PKG_VERSION")); + return Ok(()); + } + + let config_dir = + path_processing::process_path(&args.config_dir).map_err(Error::ProcessDirPath)?; + let config_path = config_dir.join("config.txt"); + let config_file = std::fs::read_to_string(&config_path).map_err(Error::ReadConfigFile)?; + let config = config::parse_config(&config_file).map_err(Error::ParseConfig)?; + + logging::setup_logging(if args.debug { + log::LevelFilter::Trace + } else { + log::LevelFilter::Warn + }); + + log::debug!("Starting with args: {args:#?}"); + log::debug!("Read config: {config:#?}"); + + let state = State::new(&config_dir, config)?; + + log::debug!("Read inventory: {:#?}", state.inventory); + + read(&state)?; Ok(()) } @@ -53,15 +946,14 @@ struct Meta { } /// Scan workspace and display its tree. -async fn read(state: &State) -> Result<(), Error> { - let root = Node::new( - "/".into(), +fn read(state: &State) -> Result<(), Error> { + let mut filetree = Filetree::new( + "/", Meta { blacklist: None, inventory: None, }, ); - let mut filetree = Filetree::new(root); let mut processing_buffer = Vec::new(); @@ -77,14 +969,13 @@ async fn read(state: &State) -> Result<(), Error> { let mut processing_buffer = VecDeque::from(processing_buffer); while let Some(item) = processing_buffer.pop_front() { - tracing::debug!("Adding {:?} to tree", item); + log::debug!("Adding {:?} to tree", item); let allowed_to_scan = allowed_to_scan(state, &item)?; let inventory_message = (|| -> Result<Option<Option<String>>, Error> { - let Some(node) = state.inventory_tree.node_by_path(&item) else { + let Some(node) = state.inventory_tree.get_node_by_path(&item) else { return Ok(None); }; - let node = node.try_borrow()?; if !node.is_leaf() { return Ok(None); } @@ -93,19 +984,22 @@ async fn read(state: &State) -> Result<(), Error> { }; Ok(Some(match check { inventory::Check::None => None, - inventory::Check::Shell(cmd) => Some(String::from_utf8( - std::process::Command::new("sh") - .arg("-c") - .arg(cmd) - .output() - .map_err(Error::Command)? - .stdout, - )?), + inventory::Check::Shell(cmd) => Some( + String::from_utf8( + std::process::Command::new("sh") + .arg("-c") + .arg(cmd) + .output() + .map_err(Error::Command)? + .stdout, + ) + .map_err(Error::StringUtf8)?, + ), })) })()?; - if let Some(node) = filetree.node_by_path(&item) { - node.try_borrow_mut()?.meta_mut().blacklist = Some(!allowed_to_scan); + if let Some(meta) = filetree.get_meta_mut_by_path(&item) { + meta.blacklist = Some(!allowed_to_scan); } else { // The earliest prefix that already added let mut base = item.clone(); @@ -122,30 +1016,34 @@ async fn read(state: &State) -> Result<(), Error> { for component in suffix.components() { base.push(component); - filetree.insert(Node::new( - base.clone(), + filetree + .insert( + base.clone(), + Meta { + blacklist: None, + inventory: None, + }, + ) + .map_err(Error::Filetree)?; + } + filetree + .insert( + item.clone(), Meta { - blacklist: None, - inventory: None, + blacklist: Some(!allowed_to_scan), + inventory: inventory_message, }, - ))?; - } - filetree.insert(Node::new( - item.clone(), - Meta { - blacklist: Some(!allowed_to_scan), - inventory: inventory_message, - }, - ))?; + ) + .map_err(Error::Filetree)?; } - if allowed_to_scan && should_scan(state, &item)? { - tracing::debug!("Scanning {:?}", item.file_name()); - let mut iter = tokio::fs::read_dir(&item).await.map_err(Error::ReadFs)?; + if allowed_to_scan && should_scan(state, &item) { + log::debug!("Scanning {:?}", item.file_name()); + let mut iter = std::fs::read_dir(&item).map_err(Error::ReadFs)?; let mut dir_buffer = Vec::new(); - while let Some(result) = iter.next_entry().await.map_err(Error::ReadFs)? { + while let Some(result) = iter.next().transpose().map_err(Error::ReadFs)? { let path = result.path(); dir_buffer.push(path) } @@ -165,16 +1063,16 @@ async fn read(state: &State) -> Result<(), Error> { /// Does not check if allowed. /// Please check if allowed before doing this. -fn should_scan(state: &State, path: &Path) -> Result<bool, Error> { +fn should_scan(state: &State, path: &Path) -> bool { // is allowed // and is a prefix of a blacklist that is not overridden or is a prefix of an inventory item - let inventory = if let Some(node) = state.inventory_tree.node_by_path(path) { - !node.try_borrow()?.is_leaf() + let inventory = if let Some(node) = state.inventory_tree.get_node_by_path(path) { + !node.is_leaf() } else { false }; - Ok(state.blacklist_tree.check_path(path) || inventory) + state.blacklist_tree.check_path(path) || inventory } /// If a directory is allowed to be scanned. @@ -206,23 +1104,18 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> { let mut print_buffer = VecDeque::new(); println!( "{}", - tree.root() - .try_borrow()? - .path() - .to_str() - .unwrap_or("UNKNOWN") - .to_owned() + tree.root().path().to_str().unwrap_or("UNKNOWN").to_owned() ); print_buffer.extend( tree.root() - .try_borrow()? .children() + .iter() + .copied() .map(|child_id| (1, child_id)), ); while let Some((offset, node_id)) = print_buffer.pop_front() { - let node = tree.node(node_id).expect("Shold have the node"); + let node = tree.get_node(node_id).expect("Shold have the node"); let name = node - .try_borrow()? .path() .file_name() .map(|name| name.to_str()) @@ -230,22 +1123,23 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> { .unwrap_or("UNKNOWN") .to_owned(); let offset_text = " ".repeat(offset); - if let Some(msg) = node.try_borrow()?.meta().inventory.as_ref() { + if let Some(msg) = node.meta().inventory.as_ref() { let msg = msg.as_deref().unwrap_or("\x1b[31mno info\x1b[0m"); println!( "{}\x1b[2m|\x1b[0m \x1b[32m{}\x1b[0m \x1b[3m{}\x1b[0m", offset_text, name, msg, ); - } else if let Some(true) = node.try_borrow()?.meta().blacklist { + } else if let Some(true) = node.meta().blacklist { println!("{}\x1b[2m|\x1b[0m \x1b[2;9m{}\x1b[0m", offset_text, name); - } else if node.try_borrow()?.is_leaf() { + } else if node.is_leaf() { println!("{}\x1b[2m|\x1b[0m \x1b[31m{}\x1b[0m", offset_text, name); } else { println!("{}\x1b[2m|\x1b[0m {}", offset_text, name); } for child in node - .try_borrow()? .children() + .iter() + .copied() .map(|child_id| (offset + 1, child_id)) .collect::<Vec<_>>() .into_iter() @@ -257,30 +1151,50 @@ fn print(tree: &Filetree<Meta>) -> Result<(), Error> { Ok(()) } -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum Error { - #[error(transparent)] - Config(#[from] config::Error), - #[error(transparent)] - State(#[from] state::Error), - #[error("Error processing dir path: {0}")] + ParseArgs(args::Error), ProcessDirPath(path_processing::Error), - #[error("Error setting up logging: {0}")] - Logging(#[from] logging::Error), - #[error("Error building filetree: {0}")] - Filetree(#[from] filetree::Error), - #[error("Borrow error: {0}")] - Borrow(#[from] BorrowError), - #[error("Borrow mut error: {0}")] - BorrowMut(#[from] BorrowMutError), - #[error("Error stripping base from path: {0}")] - BasePrefix(StripPrefixError), - #[error("No inventory check info on tree node")] + ReadConfigFile(std::io::Error), + ParseConfig(config::Error), + Filetree(filetree::Error), + BasePrefix(std::path::StripPrefixError), NoInventoryCheckInfo, - #[error("Error executing command: {0}")] Command(std::io::Error), - #[error("String returned by command is not utf8: {0}")] - StringUtf8(#[from] FromUtf8Error), - #[error("Error reading filesystem: {0}")] - ReadFs(tokio::io::Error), + StringUtf8(std::string::FromUtf8Error), + ReadFs(std::io::Error), +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::ParseArgs(error) => Some(error), + Error::ProcessDirPath(error) => Some(error), + Error::ReadConfigFile(error) => Some(error), + Error::ParseConfig(error) => Some(error), + Error::Filetree(error) => Some(error), + Error::BasePrefix(error) => Some(error), + Error::NoInventoryCheckInfo => None, + Error::Command(error) => Some(error), + Error::StringUtf8(error) => Some(error), + Error::ReadFs(error) => Some(error), + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::ParseArgs(error) => write!(f, "Error parsing args: {error}"), + Error::ProcessDirPath(error) => write!(f, "Error processing directory path: {error}"), + Error::ReadConfigFile(error) => write!(f, "Error reading config file: {error}"), + Error::ParseConfig(error) => write!(f, "Error parsing config: {error}"), + Error::Filetree(error) => write!(f, "Filetree error: {error}"), + Error::BasePrefix(error) => write!(f, "Error stripping prefix: {error}"), + Error::NoInventoryCheckInfo => write!(f, "No inventory check info in filetree"), + Error::Command(error) => write!(f, "Error executing command: {error}"), + Error::StringUtf8(error) => write!(f, "Error building utf8 string: {error}"), + Error::ReadFs(error) => write!(f, "Error reading from filesystem: {error}"), + } + } } diff --git a/src/logging.rs b/src/logging.rs @@ -1,86 +0,0 @@ -use tracing::Level; -use tracing_subscriber::{ - Layer, - fmt::writer::BoxMakeWriter, - layer::{Filter, SubscriberExt}, - util::SubscriberInitExt, -}; - -use crate::config::logging::{LogFormat, LoggingConfig, SubscriberConfig, TargetFilterType}; - -/// Sets up a log implementation that logs to stdout. -pub fn setup_logging(config: &LoggingConfig) -> Result<(), Error> { - let file_layer = config.file.as_ref().map(|config| { - let writer = BoxMakeWriter::new(tracing_appender::rolling::RollingFileAppender::new( - tracing_appender::rolling::Rotation::NEVER, - &config.directory, - &config.prefix, - )); - fmt_subscriber(writer, &config.subscriber) - }); - let mut subscribers = Vec::new(); - if let Some(file_layer) = file_layer { - subscribers.push(file_layer.boxed()); - } - tracing_subscriber::registry() - .with(subscribers) - .try_init() - .map_err(|error| Error::TracingSubscriber(error.to_string()))?; - Ok(()) -} - -fn fmt_subscriber<S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>>( - writer: BoxMakeWriter, - config: &SubscriberConfig, -) -> Option<impl Layer<S> + 'static> { - let fmt = tracing_subscriber::fmt::layer() - .with_writer(writer) - .with_ansi(config.ansi) - .with_target(config.target) - .with_file(config.file_path) - .with_line_number(config.line_number); - let fmt = match config.format { - LogFormat::Plain => fmt.boxed(), - LogFormat::Json => fmt.json().boxed(), - }; - let fmt = fmt - .with_filter(tracing_subscriber::filter::LevelFilter::from_level( - Option::<Level>::from(config.level)?, - )) - .with_filter(subscriber_target_filter(config)); - Some(fmt.boxed()) -} - -fn subscriber_target_filter<S: tracing::Subscriber>( - config: &SubscriberConfig, -) -> impl Filter<S> + 'static { - let whitelist_filters = config - .target_filters - .clone() - .into_iter() - .filter(|filter| matches!(filter.filter_type, TargetFilterType::Whitelist)) - .collect::<Vec<_>>(); - let blacklist_filters = config - .target_filters - .clone() - .into_iter() - .filter(|filter| matches!(filter.filter_type, TargetFilterType::Blacklist)) - .collect::<Vec<_>>(); - tracing_subscriber::filter::FilterFn::new(move |log| { - let whitelisted = whitelist_filters.is_empty() - || whitelist_filters - .iter() - .any(|filter| log.target().contains(&filter.pattern)); - let blacklisted = !blacklist_filters.is_empty() - && blacklist_filters - .iter() - .any(|filter| log.target().contains(&filter.pattern)); - whitelisted && !blacklisted - }) -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Error setting global subscriber: {0}")] - TracingSubscriber(String), -} diff --git a/src/main.rs b/src/main.rs @@ -1,5 +1,5 @@ fn main() { - if let Err(error) = jannie::jannie_new::oneshot() { + if let Err(error) = jannie::oneshot() { eprintln!("{error}"); } } diff --git a/src/path_processing.rs b/src/path_processing.rs @@ -1,45 +0,0 @@ -use std::{ - env::VarError, - path::{Path, PathBuf}, -}; - -pub fn expand_path(path: &Path) -> Result<PathBuf, Error> { - let path = shellexpand::full( - path.to_str() - .ok_or_else(|| Error::Parse(path.to_path_buf()))?, - )?; - Ok(path.as_ref().into()) -} - -pub fn canonicalize_path(path: &Path) -> Result<PathBuf, Error> { - let path = path.canonicalize().map_err(Error::Canonicalize)?; - Ok(path) -} - -/// Expands given path and canonicalizes it -pub fn process_path(path: &Path) -> Result<PathBuf, Error> { - let path = expand_path(path)?; - canonicalize_path(&path) -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Error parsing path: {0:?}")] - Parse(PathBuf), - #[error("Error expanding path: {0}")] - Expand(#[from] shellexpand::LookupError<VarError>), - #[error("Error canonicalizing path: {0}")] - Canonicalize(std::io::Error), -} - -impl Error { - pub fn is_file_not_found(&self) -> bool { - match *self { - Error::Canonicalize(ref error) => match error.kind() { - std::io::ErrorKind::NotFound => true, - _ => false, - }, - _ => false, - } - } -} diff --git a/src/state.rs b/src/state.rs @@ -1,194 +0,0 @@ -use std::path::{Path, PathBuf}; - -use tokio::runtime::Runtime; - -use crate::{ - config::{Config, Filter, InventoryFormat}, - filetree::{self, Filetree, node::Node}, - inventory::{self, Inventory}, - path_processing, -}; - -pub struct State { - pub config_dir: PathBuf, - pub config: Config, - pub runtime: Runtime, - pub inventory: Inventory, - /// Paths are absolute and processed - pub filters: Vec<Filter>, - /// Absolute processed - pub root: PathBuf, - pub blacklist_tree: Filetree<()>, - pub whitelist_tree: Filetree<()>, - pub inventory_tree: Filetree<Option<inventory::Check>>, -} - -impl State { - pub fn new(config_dir: &Path, config: Config) -> Result<Self, Error> { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(Error::Runtime)?; - - let inventory_path = match config.inventory_format { - InventoryFormat::Yaml => config_dir.join("inventory.yaml"), - InventoryFormat::Json => config_dir.join("inventory.json"), - }; - - tracing::trace!("Reading inventory from {inventory_path:?}"); - - let inventory = runtime - .block_on(tokio::fs::read(inventory_path)) - .map_err(Error::ReadInventoryFile)?; - - tracing::trace!("Parsing inventory file contents"); - - let inventory: Inventory = - match config.inventory_format { - InventoryFormat::Yaml => serde_yaml::from_slice(inventory.as_slice()) - .map_err(Error::ReadInventoryYaml)?, - InventoryFormat::Json => serde_json::from_slice(inventory.as_slice()) - .map_err(Error::ReadInventoryJson)?, - }; - - tracing::trace!("Processing root path"); - - let root = path_processing::process_path(&config.root).map_err(Error::ProcessRootPath)?; - - let mut filters = Vec::new(); - - tracing::debug!("Preprocessing filters"); - - for filter in &config.filters { - tracing::trace!("Preprocessing filter {filter:?}"); - let mut path = config.root.clone(); - path.push(&filter.path); - filters.push(Filter { - path: match path_processing::process_path(&path) { - Ok(path) => path, - Err(error) if error.is_file_not_found() => { - tracing::warn!("Filter path was not found: {filter:?}"); - continue; - } - Err(error) => Err(Error::ProcessPath(error))?, - }, - filter_type: filter.filter_type.clone(), - }); - } - - tracing::debug!("Building trees from filters"); - - let mut blacklist_tree = Filetree::new(Node::new("/".into(), ())); - let mut whitelist_tree = Filetree::new(Node::new("/".into(), ())); - - for filter in &filters { - let path = &filter.path; - match filter.filter_type { - crate::config::FilterType::Whitelist => { - tracing::debug!("Adding {:?} to whitelist", path); - add_to_tree(&mut whitelist_tree, &path)? - } - crate::config::FilterType::Blacklist => { - if filters - .iter() - .filter(|filter| filter.is_whitelist()) - .find(|filter| &filter.path == path) - .is_some() - { - tracing::debug!("Skipping {:?} blacklist", path); - } else { - tracing::debug!("Adding {:?} to blacklist", path); - add_to_tree(&mut blacklist_tree, &path)? - } - } - } - } - - tracing::debug!("Building inventory tree"); - - let mut inventory_tree = Filetree::new(Node::new("/".into(), None)); - for item in &inventory.items { - let path = match path_processing::process_path(&item.path) { - Ok(path) => path, - Err(error) if error.is_file_not_found() => { - tracing::warn!("Inventory item was not found: {item:?}"); - continue; - } - Err(error) => Err(Error::ProcessPath(error))?, - }; - let mut components = path.components(); - let mut path_buf = PathBuf::new(); - loop { - let component = components.next(); - match component { - Some(component) => { - path_buf.push(component); - if inventory_tree.node_by_path(&path_buf).is_none() { - inventory_tree.insert(Node::new(path_buf.clone(), None))?; - } - } - None => { - inventory_tree - .node_by_path(&path) - .expect("Should have added inventory item") - .borrow_mut() - .meta_mut() - .replace(item.check.clone()); - break; - } - } - } - } - - Ok(Self { - config_dir: config_dir.into(), - config, - runtime, - inventory, - filters, - root, - blacklist_tree, - whitelist_tree, - inventory_tree, - }) - } -} - -fn add_to_tree(tree: &mut Filetree<()>, path: &Path) -> Result<(), Error> { - let mut components = path.components(); - let mut path_buf = PathBuf::new(); - loop { - let component = components.next(); - match component { - Some(component) => { - path_buf.push(component); - if tree.node_by_path(&path_buf).is_none() { - tree.insert(Node::new(path_buf.clone(), ()))?; - } - } - None => { - assert!(tree.node_by_path(path).is_some()); - break; - } - } - } - Ok(()) -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Error building runtime: {0}")] - Runtime(tokio::io::Error), - #[error("Error reading inventory file: {0}")] - ReadInventoryFile(std::io::Error), - #[error("Error processing path: {0}")] - ProcessPath(path_processing::Error), - #[error("Error processing root path: {0}")] - ProcessRootPath(path_processing::Error), - #[error("Error parsing yaml inventory: {0}")] - ReadInventoryYaml(serde_yaml::Error), - #[error("Error parsing json inventory: {0}")] - ReadInventoryJson(serde_json::Error), - #[error("Error building filetree: {0}")] - Filetree(#[from] filetree::Error), -}