jannie

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 6d684d1c56f5ff14f07ff3dc0c2c9e8dafad0098
Author: Andy Khramtsov <>
Date:   Sat,  7 Feb 2026 01:53:13 +0300

feat: init

Diffstat:
A.gitignore | 2++
ACargo.lock | 1244+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACargo.toml | 18++++++++++++++++++
Aconfig.yaml | 19+++++++++++++++++++
Ainventory.json | 0
Ajustfile | 8++++++++
Asrc/args.rs | 8++++++++
Asrc/config/logging.rs | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/config/mod.rs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/filetree/mod.rs | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/filetree/node.rs | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib.rs | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/logging.rs | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main.rs | 5+++++
Asrc/state.rs | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15 files changed, 2117 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +/target +log diff --git a/Cargo.lock b/Cargo.lock @@ -0,0 +1,1244 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "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 = "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 = "0.0.0" +dependencies = [ + "clap", + "config", + "dialoguer", + "indexmap", + "serde", + "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 = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "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 = "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 = "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 = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "test-log" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4" +dependencies = [ + "env_logger", + "test-log-macros", + "tracing-subscriber", +] + +[[package]] +name = "test-log-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "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" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "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 @@ -0,0 +1,18 @@ +[package] +name = "jannie" +version = "0.0.0" +edition = "2024" + +[dependencies] +clap = { version = "4.5.54", features = ["derive"] } +config = "0.15.19" +dialoguer = "0.12.0" +indexmap = "2.13.0" +serde = { version = "1.0.228", features = ["derive"] } +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 @@ -0,0 +1,19 @@ +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/rust/jannie + type: blacklist + - path: my-space/projects/rust/jannie/log + type: whitelist diff --git a/inventory.json b/inventory.json diff --git a/justfile b/justfile @@ -0,0 +1,8 @@ +set quiet := true + +default: + just --list + +# Nextest all workspace packages +test: + RUST_LOG=trace cargo nextest run --workspace --no-fail-fast diff --git a/src/args.rs b/src/args.rs @@ -0,0 +1,8 @@ +use std::path::PathBuf; + +#[derive(clap::Parser, Debug)] +pub struct Args { + /// Configuration and inventory + #[arg(short, long, default_value = ".")] + pub dir: PathBuf, +} diff --git a/src/config/logging.rs b/src/config/logging.rs @@ -0,0 +1,103 @@ +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 @@ -0,0 +1,63 @@ +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 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, 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 @@ -0,0 +1,195 @@ +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) + } +} + +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 @@ -0,0 +1,72 @@ +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 + } + + /// 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/lib.rs b/src/lib.rs @@ -0,0 +1,202 @@ +use std::{cell::BorrowError, collections::VecDeque, path::Path}; + +use clap::Parser; + +use crate::{ + args::Args, + config::Config, + filetree::{Filetree, node::Node}, + state::State, +}; + +pub mod args; +pub mod config; +pub mod filetree; +pub mod logging; +pub mod state; + +pub fn result_main() -> Result<(), Error> { + let args = Args::parse(); + let config_path = args.dir.join("config.yaml"); + let config = Config::new(&config_path)?; + + logging::setup_logging(&config.logging)?; + + tracing::debug!("Starting with args: {args:#?}"); + tracing::debug!("Read config: {config:#?}"); + + let state = State::new(&args.dir, config)?; + + state.runtime.block_on(read(&state)); + + Ok(()) +} + +#[derive(Clone, Debug)] +struct Meta { + blacklist: Option<bool>, +} + +async fn read(state: &State) { + let root = Node::new(state.config.root.clone(), Meta { blacklist: None }); + let mut filetree = Filetree::new(root); + let mut buffer = Vec::new(); + + buffer.extend( + state + .config + .filters + .iter() + .filter(|filter| filter.is_whitelist()) + .map(|filter| { + let mut path = state.config.root.clone(); + path.push(&filter.path); + path + }), + ); + + // This makes it so every prefix goes first. + buffer.sort(); + + let mut buffer = VecDeque::from(buffer); + + while let Some(dir) = buffer.pop_front() { + tracing::debug!("Adding {:?} to tree", dir); + let mut base = dir.clone(); + while filetree.node_by_path(&base).is_none() { + base.pop(); + } + + let allowed = allowed(state, &dir); + + if base != dir { + let mut suffix = dir.strip_prefix(&base).unwrap().to_owned(); + suffix.pop(); + + for component in suffix.components() { + base.push(component); + filetree + .insert(Node::new(base.clone(), Meta { blacklist: None })) + .unwrap(); + } + filetree + .insert(Node::new( + dir.clone(), + Meta { + blacklist: Some(!allowed), + }, + )) + .unwrap(); + } else { + filetree + .node_by_path(&dir) + .unwrap() + .borrow_mut() + .meta_mut() + .blacklist = Some(!allowed); + } + + if allowed && should_scan(state, &dir).unwrap() { + tracing::debug!("Scanning {:?}", dir.file_name()); + let mut iter = tokio::fs::read_dir(&dir).await.unwrap(); + + while let Some(result) = iter.next_entry().await.unwrap() { + let path = result.path(); + buffer.push_back(path) + } + } + } + + print(&filetree).unwrap(); +} + +/// Does not check if allowed. +/// Please check if allowed before doing this. +fn should_scan(state: &State, path: &Path) -> Result<bool, Error> { + // 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() + } else { + false + }; + + Ok(state.blacklist_tree.node_by_path(path).is_some() || inventory) +} + +/// If a directory is allowed to be scanned. +/// Whitelist has more priority than blacklist on the same level of specificity. +fn allowed(state: &State, path: &Path) -> bool { + // If there is no not-overridden blacklist + + let path = path.strip_prefix(&state.config.root).unwrap(); + + // If no such blacklist filter found such that + // for this blacklist filter no such whitelist found + // for which the blacklist is the prefix + !state + .config + .filters + .iter() + .filter(|filter| filter.is_blacklist()) + .any(|bl_filter| { + path.starts_with(&bl_filter.path) + && !state + .config + .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 = Vec::new(); + print_buffer.push((0, tree.root().borrow().id())); + while let Some((offset, node_id)) = print_buffer.pop() { + let node = tree.node(node_id).expect("Shold have the node"); + match node.borrow().meta().blacklist { + Some(true) => println!( + "{}| {}, BL", + " ".repeat(offset), + node.borrow() + .path() + .file_name() + .map(|name| name.to_str()) + .flatten() + .unwrap_or("UNKNOWN") + ), + _ => println!( + "{}| {}", + " ".repeat(offset), + node.borrow() + .path() + .file_name() + .map(|name| name.to_str()) + .flatten() + .unwrap_or("UNKNOWN") + ), + }; + print_buffer.extend( + node.borrow() + .children() + .map(|child_id| (offset + 1, child_id)), + ); + } + Ok(()) +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] config::Error), + #[error(transparent)] + State(#[from] state::Error), + #[error("Error setting up logging: {0}")] + Logging(#[from] logging::Error), + #[error("Borrow error: {0}")] + Borrow(#[from] BorrowError), +} diff --git a/src/logging.rs b/src/logging.rs @@ -0,0 +1,86 @@ +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 @@ -0,0 +1,5 @@ +fn main() { + if let Err(error) = jannie::result_main() { + eprintln!("{error}"); + } +} diff --git a/src/state.rs b/src/state.rs @@ -0,0 +1,92 @@ +use std::path::{Path, PathBuf}; + +use tokio::runtime::Runtime; + +use crate::{ + config::Config, + filetree::{self, Filetree, node::Node}, +}; + +pub struct State { + pub config_dir: PathBuf, + pub config: Config, + pub runtime: Runtime, + pub blacklist_tree: Filetree<()>, + pub whitelist_tree: Filetree<()>, + pub inventory_tree: Filetree<()>, +} + +impl State { + pub fn new(config_dir: &Path, config: Config) -> Result<Self, Error> { + // todo: read inventory + let mut blacklist_tree = Filetree::new(Node::new("/".into(), ())); + let mut whitelist_tree = Filetree::new(Node::new("/".into(), ())); + + for filter in &config.filters { + let mut path = config.root.clone(); + path.push(&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 config + .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)? + } + } + } + } + + let inventory_tree = Filetree::new(Node::new("/".into(), ())); + Ok(Self { + config_dir: config_dir.into(), + config, + runtime: tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(Error::Runtime)?, + 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 building filetree: {0}")] + Filetree(#[from] filetree::Error), +}