diff --git a/.gitignore b/.gitignore index 0104787..582e1c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Generated by Cargo # will have compiled files and executables -debug/ -target/ +debug/** +target/** # These are backup files generated by rustfmt **/*.rs.bk @@ -9,9 +9,12 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +# Build - Test: +*.txt + +# Develop +TODO.md + +# Releases (realAscot) +releases/** +bin/** diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7e65fe4 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cargo", + "command": "check", + "problemMatcher": [ + "$rustc" + ], + "group": "build", + "label": "rust: cargo check" + }, + { + "type": "cargo", + "command": "clean", + "problemMatcher": [ + "$rustc" + ], + "group": "clean", + "label": "rust: cargo clean" + }, + { + "type": "cargo", + "command": "build", + "problemMatcher": [ + "$rustc" + ], + "group": "build", + "label": "rust: cargo build" + } + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ed86fcc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog treeScanner (Rust) + +- **2025-05-17 - v1.0.0** + - **Geändert:** + - [x] Komplette Neuumsetzung des TreeScanner-Tools in Rust + - [x] Aufbau einer modularen Projektstruktur (`treebuilder.rs`, `args.rs`, `loader.rs`, `ascii_spinner.rs`, `logger.rs`) + - [x] CLI-Parsing mit `clap` umgesetzt, inkl. Aliase und Hilfetexte + - [x] Fortschrittsanzeige mit ASCII-Spinner als wiederverwendbares Modul integriert + - [x] Unterstützung für `.treescanner.conf` im Home-Verzeichnis eingebaut (inkl. Auto-Erzeugung und TOML-Validierung) + - [x] Optionale Parameter wie `--max-depth`, `--viewonly`, `--ignore`, `--align-comments` implementiert + - [x] Fallback-Logik: CLI-Eingaben überschreiben Konfiguration bei Kollision + - [x] Ausgabe wahlweise in Konsole oder Datei (Standard: `tree.txt`) + - [x] Unit-Tests für Konfiguration und Kommentar-Ausrichtung hinzugefügt (`tests/config_tests.rs`) + - [x] Fehlerausgaben für ungültige Konfiguration oder fehlende Verzeichnisse eingebaut + - [x] Makefile mit Windows-Kompatibilität für Build + Copy-Ziel erstellt + - [x] Icon und Metadaten in .exe eingebettet via `build.rs` und `rc.exe` + + - **Hinzugefügt:** + - [x] CLI-Argument `--ignore` unterstützt sowohl Kommaseparierung (CLI) als auch Listen (TOML) + - [x] Debug- und Silent-Modus zur Steuerung der Ausgaben + - [x] Unterstützung für Konfigurationsparameter `output`, `viewonly`, `language`, `align_comments` + - [x] Automatische Erstellung einer gültigen Standardkonfiguration, wenn keine `.treescanner.conf` vorhanden ist + - [x] Fehler-Feedback mit TOML-Zeilenangaben bei ungültiger Konfiguration + + - **Entfernt:** + - [x] Python-Version vollständig ersetzt + - [x] Temporäre Hilfsfunktionen aus der Portierungsphase entfernt diff --git a/Cargo.lock b/Cargo.lock index a6639cb..4a27a14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,62 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + [[package]] name = "cc" version = "1.2.22" @@ -17,6 +73,73 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "embed-resource" version = "2.5.1" @@ -37,12 +160,57 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "indexmap" version = "2.9.0" @@ -53,18 +221,52 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -83,6 +285,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -92,6 +311,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "semver" version = "1.0.26" @@ -133,6 +365,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.101" @@ -144,6 +382,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.8.22" @@ -187,9 +458,14 @@ checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" [[package]] name = "treescanner" -version = "0.1.0" +version = "1.0.0" dependencies = [ + "clap", + "dirs", "embed-resource", + "serde", + "tempfile", + "toml", ] [[package]] @@ -198,6 +474,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "vswhom" version = "0.1.0" @@ -218,13 +500,37 @@ dependencies = [ "libc", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -233,13 +539,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -248,42 +570,90 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.7.10" @@ -300,5 +670,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", ] diff --git a/Cargo.toml b/Cargo.toml index 8a6f2fa..04621a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,19 @@ [package] name = "treescanner" -version = "0.1.0" +version = "1.0.0" edition = "2024" +[package.metadata.winres] +subsystem = "console" + [build-dependencies] embed-resource = "2.4" + +[dependencies] +clap = { version = "4.5", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +toml = "0.8" +dirs = "5" + +[dev-dependencies] +tempfile = "3" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e68ca1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +MIT License + +Copyright (c) 2025 Adam Skotarczak + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + +2. Attribution Requirement: + If the Software is forked, reused, or substantially modified and redistributed, + visible attribution must be provided in the documentation (e.g., README file), stating: + “This software is based on work by Adam Skotarczak (https://github.com/realAscot)”. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cea7f74 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +# ======================================= +# 🦀 Windows-kompatibles Makefile für TreeScanner +# ======================================= + +PROJECT_NAME := treescanner +BUILD_DIR := target/release +OUT_DIR := bin + +# Standardziel +.PHONY: all +all: build copy + +# Release-Build +.PHONY: build +build: + cargo build --release + +# Kopiere EXE in bin\ +.PHONY: copy +copy: + if not exist "$(OUT_DIR)" mkdir "$(OUT_DIR)" + copy /Y "$(BUILD_DIR)\$(PROJECT_NAME).exe" "$(OUT_DIR)\$(PROJECT_NAME).exe" + +# Lösche alles außer bin\ +.PHONY: clean +clean: + cargo clean + +# Tests +.PHONY: test +test: + cargo test + +# Lokale Installation +.PHONY: install +install: + cargo install --path . --root install-local + +# Vollständiger Reset +.PHONY: reset +reset: clean + if exist "$(OUT_DIR)" rmdir /S /Q "$(OUT_DIR)" + if exist "install-local" rmdir /S /Q "install-local" diff --git a/README.md b/README.md index 8a87781..341ba32 100644 --- a/README.md +++ b/README.md @@ -2,33 +2,208 @@ ![TreeScanner-Logo](./media/logo-treescanner_512x512.png) -Dieses Tool ist im Rahmen meines persönlichen Projekts von C nach Rust zu wechseln. +**TreeScanner** ist ein leichtgewichtiges, portables CLI-Tool zur rekursiven Analyse von Verzeichnisstrukturen. +Es erzeugt eine klar strukturierte ASCII-Ausgabe und eignet sich hervorragend für Dokumentation, Debugging oder Buildsysteme. + +> 🔧 Diese Version ist eine komplette Neuentwicklung in **Rust** und ersetzt das ursprüngliche Python-Projekt: +> ➜ [treeScannerASCII (Python)](https://github.com/realAscot/treeScannerASCII) + +Der original treeScanner in Python ist unter zu finden. Dieser ist auch als Python-Modul zu verwenden. + +--- ## Inhalt - [TreeScanner CLI Verzeichnisscanner](#treescanner-cli-verzeichnisscanner) - [Inhalt](#inhalt) + - [Beschreibung](#beschreibung) + - [Installation](#installation) + - [Über .zip Archiv](#über-zip-archiv) + - [Installer](#installer) - [Struktur](#struktur) - - [Features](#features) + - [✨ Features](#-features) + - [▶️ Verwendung](#️-verwendung) + - [🖼 Beispielausgabe](#-beispielausgabe) + - [⚙️ Konfiguration `.treescanner.conf`](#️-konfiguration-treescannerconf) + - [🔍 Ort](#-ort) + - [📘 Format](#-format) + - [📝 Format (.toml)](#-format-toml) - [Lizenz](#lizenz) + - [Eingesetzte Libraries (MIT-kompatibel):](#eingesetzte-libraries-mit-kompatibel) + - [💬 Kontakt](#-kontakt) + +--- + +## Beschreibung + +Der treeScanner.exe ist ursprünglich als ein Tool entwickelt worden mit dem man Verzeichnisstrukturen für Dokumentationen erzeugen konnte. +TreeScanner durchsucht Verzeichnisse rekursiv, filtert optional bestimmte Ordner aus und gibt eine **strukturierte ASCII-Baumdarstellung** mit Icons und optional ausgerichteten Kommentaren aus. +Er eignet sich für technische Dokumentationen, Versionskontrollen, Release-Skripte und CI/CD-Prozesse. + +--- + +## Installation + +### Über .zip Archiv + +### Installer + +Der Installer `treeScanner-Setup.exe` der jeweils die aktuelle Version auf GitHub enthällt, bietet Dir an das Programm im Windows-Verzeichnis zu installieren. +Dies ist zwar unüblich aber --- ## Struktur -**Diese Struktur ist eine Ausgabe des Tools:** +**GEPLANTE STRUKTUR (DEV)** ```plaintext +📁 treeScanner # +├── 📁 .cargo # +│ └── 📄 config.toml # +├── 📁 .vscode # +│ └── 📄 tasks.json # +├── 📁 media # +│ ├── 📄 logo-treescanner.png # +│ └── 📄 logo-treescanner_512x512.png # +├── 📁 resources # +│ ├── 📄 icon.ico # +│ └── 📄 version.rc # +├── 📁 src # +│ ├── 📁 app # +│ │ ├── 📄 mod.rs # +│ │ └── 📄 treebuilder.rs # +│ ├── 📁 config # +│ │ ├── 📄 args.rs # +│ │ ├── 📄 loader.rs # +│ │ └── 📄 mod.rs # +│ ├── 📁 utils # +│ │ ├── 📄 ascii_spinner.rs # +│ │ ├── 📄 logger.rs # +│ │ └── 📄 mod.rs # +│ ├── 📄 lib.rs # +│ └── 📄 main.rs # +├── 📁 tests # +│ ├── 📄 config_tests.rs # +│ └── 📄 treebuilder_tests.rs # +├── 📄 .gitignore # +├── 📄 build.rs # +├── 📄 Cargo.lock # +├── 📄 Cargo.toml # +├── 📄 CHANGELOG.md # +├── 📄 LICENSE # +├── 📄 Makefile # +├── 📄 README.md # +└── 📄 VERSION # ``` --- -## Features +## ✨ Features + +- 📁 ASCII-Baumdarstellung mit Icons (📁, 📄) +- 🚫 Ignorierliste per CLI oder Konfig-Datei +- ⏫ Limit für Tiefe (`--max-depth`) und Dateianzahl pro Verzeichnis +- 📄 Ausgabe in Datei oder Konsole +- ⚙ Konfigurierbar via `~/.treescanner.conf` +- 🌀 Fortschrittsanzeige beim Scannen (Spinner) +- 💬 Optionale Kommentarspalte (`--align-comments`) +- 🧪 Getestete Komponenten (unit-tested) +- 🔕 Silent-Modus (`--quiet`) +- 🛠 Portable Binary (`.exe`) ohne externe Abhängigkeiten + +--- + +## ▶️ Verwendung + +```bash +# Einfacher Scan (aktuelles Verzeichnis) +./treescanner.exe + +# Mit Tiefe 3, ohne speichern +./treescanner.exe --max-depth 3 --viewonly + +# Mit Kommentar-Ausrichtung +./treescanner.exe --align-comments + +# Ergebnis in Datei mit anderem Pfad speichern +./treescanner.exe --output ./struktur/tree.md +``` + +--- + +## 🖼 Beispielausgabe + +```plaintext + +📁 ./src/ +├── 📄 main.rs # +├── 📁 app/ # +│ └── 📄 treebuilder.rs # +└── 📁 utils/ # + ├── 📄 ascii_spinner.rs # + └── 📄 logger.rs # + +``` + +--- + +## ⚙️ Konfiguration `.treescanner.conf` + +### 🔍 Ort + +Standardmäßig gesucht im **Benutzerverzeichnis**: + +```plaintext +Windows: C:\Users\\.treescanner.conf +Linux: /home//.treescanner.conf +``` + +### 📘 Format + +### 📝 Format (.toml) + +```toml +max_depth = 3 +max_files_per_dir = 100 +ignore = [".git", "target", ".vscode"] +output = "tree.txt" +viewonly = false +align_comments = false +``` + +- CLI-Einstellungen überschreiben Konfigurationswerte bei Kollision +- Die Datei wird beim ersten Start automatisch erzeugt, falls sie fehlt +- Der Pfad ist **nicht fest kodiert**, sondern dynamisch via `dirs::home_dir()` ermittelt --- ## Lizenz +Dieses Projekt steht unter der [MIT-Lizenz](./LICENSE). + +### Eingesetzte Libraries (MIT-kompatibel): + +| Crate | Lizenz | +|-----------------|------------| +| `clap` | MIT/Apache | +| `dirs` | MIT/Apache | +| `serde` | MIT/Apache | +| `serde_derive` | MIT/Apache | +| `toml` | MIT/Apache | +| `tempfile` | MIT/Apache (nur für Tests) | +| `console` | MIT | + +⚠️ Alle eingebundenen Libraries sind **MIT- oder Apache-2.0-kompatibel** und dürfen ohne Einschränkungen in proprietären oder Open-Source-Projekten verwendet werden. + +siehe [LICENSE](./LICENSE) + --- + +## 💬 Kontakt + +**Adam Skotarczak** +✉ [adam@skotarczak.net](mailto:adam@skotarczak.net) +🔗 [realAscot auf GitHub](https://github.com/realAscot) diff --git a/VERSION b/VERSION index 6e8bf73..afaf360 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 +1.0.0 \ No newline at end of file diff --git a/desktop.ini b/desktop.ini new file mode 100644 index 0000000..a3e9522 --- /dev/null +++ b/desktop.ini @@ -0,0 +1,6 @@ +[.ShellClassInfo] +IconResource=C:\Users\adam\Documents\Werkbank\Projekte\Projekte-Rust\treeScannerRust\resources\icon.ico,0 +[ViewState] +Mode= +Vid= +FolderType=Documents diff --git a/src/app/mod.rs b/src/app/mod.rs index 2767f68..9ff39ee 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,3 +1 @@ -pub fn run() { - -} +pub mod treebuilder; diff --git a/src/app/treebuilder.rs b/src/app/treebuilder.rs new file mode 100644 index 0000000..9004b9d --- /dev/null +++ b/src/app/treebuilder.rs @@ -0,0 +1,112 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +/// Struktur zur Konfiguration des Verzeichnis-Scans. +pub struct TreeBuilderConfig { + pub root_path: PathBuf, + pub max_depth: Option, + pub max_files_per_dir: usize, + pub ignored_dirs: Vec, + pub folder_icon: String, + pub file_icon: String, + pub align_comments: bool, +} + +/// Verantwortlich für das Erzeugen der ASCII-Baumstruktur. +pub struct TreeBuilder { + pub config: TreeBuilderConfig, + folder_count: usize, + file_count: usize, +} + +impl TreeBuilder { + pub fn new(config: TreeBuilderConfig) -> Self { + Self { + config, + folder_count: 0, + file_count: 0, + } + } + + /// Startet den Scan und liefert das Ergebnis als String. + pub fn build_tree(&mut self) -> Vec { + let root = self.config.root_path.clone(); + let mut lines = vec![format!("{} {}/", self.config.folder_icon, root.display())]; + self.scan_dir(&root, 0, "", &mut lines); + lines + } + + fn scan_dir(&mut self, path: &Path, depth: usize, prefix: &str, lines: &mut Vec) { + if let Some(max) = self.config.max_depth { + if depth >= max { + return; + } + } + + let entries = match fs::read_dir(path) { + Ok(entries) => entries.filter_map(Result::ok).collect::>(), + Err(_) => { + lines.push(format!("{}└── [Zugriff verweigert] {}", prefix, path.display())); + return; + } + }; + + let mut folders = vec![]; + let mut files = vec![]; + + for entry in entries { + let file_name = entry.file_name().into_string().unwrap_or_default(); + if entry.path().is_dir() { + if !self.config.ignored_dirs.contains(&file_name) { + folders.push((file_name, entry.path())); + } + } else { + files.push(file_name); + } + } + + for (idx, (name, path)) in folders.iter().enumerate() { + self.folder_count += 1; + let connector = if idx < folders.len() - 1 || !files.is_empty() { "├── " } else { "└── " }; + lines.push(format!("{}{}{} {}", prefix, connector, self.config.folder_icon, name)); + + let new_prefix = if idx < folders.len() - 1 || !files.is_empty() { + format!("{}│ ", prefix) + } else { + format!("{} ", prefix) + }; + + self.scan_dir(path, depth + 1, &new_prefix, lines); + } + + let visible_files = &files[..files.len().min(self.config.max_files_per_dir)]; + let remaining = files.len().saturating_sub(visible_files.len()); + + for (idx, name) in visible_files.iter().enumerate() { + self.file_count += 1; + let connector = if idx < visible_files.len() - 1 || remaining > 0 { "├── " } else { "└── " }; + lines.push(format!("{}{}{} {}", prefix, connector, self.config.file_icon, name)); + } + + if remaining > 0 { + lines.push(format!("{}└── ", prefix, remaining)); + } + } + + /// Gibt die Anzahl gescannter Ordner und Dateien zurück. + pub fn stats(&self) -> (usize, usize) { + (self.folder_count, self.file_count) + } + + /// Richtet die Ausgabezeilen mit Kommentarspalte aus. + pub fn align_lines_with_comments(&self, lines: &[String]) -> Vec { + let max_len = lines.iter().map(|l| l.len()).max().unwrap_or(0); + lines + .iter() + .map(|line| { + let padding = " ".repeat(max_len - line.len() + 2); + format!("{}{}#", line, padding) + }) + .collect() + } +} diff --git a/src/config/args.rs b/src/config/args.rs new file mode 100644 index 0000000..14e023c --- /dev/null +++ b/src/config/args.rs @@ -0,0 +1,59 @@ +use clap::Parser; +use std::path::PathBuf; + +/// CLI-Argumente für TreeScanner +#[derive(Parser, Debug)] +#[command( author ="Adam Skotarczak ", + version= "0.2.0", + about = "TreeScanner: Verzeichnisse als ASCII-Baum visualisieren.", + +long_about = r#" +TreeScanner ist ein leichtgewichtiges CLI-Tool zur strukturierten Darstellung von Verzeichnisinhalten. + +Funktionen: +- Ausgabe als ASCII-Baum +- Optionen für Tiefe, Datei-Limit und Ignorierlisten +- Fortschrittsanzeige im Terminal +- Unterstützung für Konfigurationsdateien und CLI + +Beispiel: +treescanner.exe --max-depth 3 --ignore .git,target +"# +)] +pub struct CliArgs { + /// Root-Verzeichnis für den Scan (Standard: aktuelles Verzeichnis) + #[arg(default_value = ".")] + pub root_path: PathBuf, + + /// Maximale Scan-Tiefe + #[arg(long)] + pub max_depth: Option, + + /// Maximale Dateianzahl pro Verzeichnis + #[arg(long, default_value_t = 100)] + pub max_files_per_dir: usize, + + /// Verzeichnisse ignorieren (z. B. .git,target) durch Komma getrennt, ohne Leerzeichen. + #[arg(short = 'x', long, value_delimiter = ',')] + pub ignore: Vec, + + /// Ausgabeziel (Standard: tree.txt) + #[arg(short = 'o', long)] + pub output: Option, + + /// Nur in Konsole anzeigen, keine Ausgabedatei speichern + #[arg(long)] + pub viewonly: bool, + + /// Debug-Modus aktivieren + #[arg(short = 'D', long)] + pub debug: bool, + + /// Keine Statusausgaben + #[arg(short = 'q', long)] + pub quiet: bool, + + /// Kommentare ausrichten (DEV: optisch instabil) + #[arg(short = 'c', long, default_value_t = false)] + pub align_comments: bool, +} diff --git a/src/config/loader.rs b/src/config/loader.rs new file mode 100644 index 0000000..8736226 --- /dev/null +++ b/src/config/loader.rs @@ -0,0 +1,87 @@ +use std::collections::HashSet; +use std::fs; +use std::io::Write; + +use serde::Deserialize; + +#[derive(Debug, Deserialize, Default)] +#[allow(dead_code)] +pub struct ConfigFile { + pub max_depth: Option, + pub max_files_per_dir: Option, + pub ignore: Option>, + pub language: Option, + pub align_comments: Option, + pub output: Option, + pub viewonly: Option, +} + +/// Lädt die Konfigurationsdatei aus dem Home-Verzeichnis (`~/.treescanner.conf`). +/// +/// Falls die Datei nicht existiert, wird eine gültige Standard-Konfigurationsdatei erstellt. +/// Bei Fehlern während des Lesens oder Parsens wird `None` zurückgegeben. +/// +/// # Rückgabewert +/// * `Some(ConfigFile)` – wenn Datei existiert und geparst werden konnte +/// * `None` – bei Fehlern oder wenn Datei nicht erstellt/parst werden konnte +pub fn load_config_from_home() -> Option { + let home = dirs::home_dir()?; + let config_path = home.join(".treescanner.conf"); + + // Versuche, die Datei zu lesen + match fs::read_to_string(&config_path) { + Ok(content) => { + match toml::from_str::(&content) { + Ok(cfg) => Some(cfg), + Err(e) => { + eprintln!("⚠️ Konfigurationsdatei ungültig (TOML-Fehler): {e}"); + println!("ℹ️ Du findest die Datei hier: {}", config_path.display()); + println!("🔁 Du kannst sie löschen, sie wird beim nächsten Start neu erstellt."); + None + } + } + } + + // Datei existiert nicht – erstelle neue Standard-Konfigurationsdatei + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + let default_config = r#" +# TreeScanner Standard-Konfiguration +# Nicht gesetzte Felder werden intern auf Defaultwerte gesetzt. + +# max_depth = 5 # default: unendlich +# max_files_per_dir = 1000 # default: 100 +ignore = [".git", ".fingerprint"] # default: "" +language = "de" +align_comments = false # default: false +output = "treescanner.txt" # default: tree.txt +viewonly = false # default: false +"#; + + let result = fs::File::create(&config_path) + .and_then(|mut f| f.write_all(default_config.trim_start().as_bytes())); + + match result { + Ok(_) => { + println!( + "\n✏️ Standard-Konfigurationsdatei erstellt: < {} >\n", + config_path.display() + ); + // Lies die frisch geschriebene Datei direkt ein + fs::read_to_string(&config_path) + .ok() + .and_then(|s| toml::from_str(&s).ok()) + } + Err(e) => { + eprintln!("⚠️ Konnte Standard-Konfigurationsdatei nicht schreiben: {e}"); + None + } + } + } + + // Anderer Lese-Fehler (z. B. Berechtigung) + Err(e) => { + eprintln!("⚠️ Fehler beim Lesen der Konfigurationsdatei: {e}"); + None + } + } +} \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..d4ee904 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,2 @@ +pub mod args; +pub mod loader; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cada94e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod app; +pub mod utils; diff --git a/src/main.rs b/src/main.rs index 2252402..880d265 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,101 @@ - -// Importiert das Modul `utils`, das vermutlich Unterfunktionen wie Fenster oder Logging enthält -mod utils; +mod config; mod app; +mod utils; + +use app::treebuilder::{TreeBuilder, TreeBuilderConfig}; +use config::args::CliArgs; +use config::loader::load_config_from_home; +use utils::ascii_spinner::start_spinner; +use clap::Parser; +use std::fs; +use std::time::Instant; +use utils::logger::init_logger; + +/// Gibt die verstrichene Zeit seit `timer` aus. +fn view_timer(timer: &Instant) { + println!("\n⏱️ Gesamtlaufzeit des Scans: {:.2?}", timer.elapsed()); +} fn main() { - // Initialisiert das Logger-System (für Debug- oder Datei-Logging - nicht implementiert) - utils::logger::init(); - app::run(); + init_logger(); + let args = CliArgs::parse(); + let file_config = load_config_from_home().unwrap_or_default(); + let timer = Instant::now(); + + if !args.root_path.is_dir() { + eprintln!("⚠️ Fehler: '{}' ist kein gültiges Verzeichnis.", args.root_path.display()); + std::process::exit(1); + } + + if let Some(parent) = args.output.as_ref().and_then(|p| p.parent()) { + if let Err(e) = fs::create_dir_all(parent) { + eprintln!("⚠️ Fehler beim Erstellen des Zielordners: {e}"); + std::process::exit(1); + } + } + + let config = TreeBuilderConfig { + root_path: args.root_path.clone(), + max_depth: args.max_depth.or(file_config.max_depth), + max_files_per_dir: if args.max_files_per_dir != 100 { + args.max_files_per_dir + } else { + file_config.max_files_per_dir.unwrap_or(100) + }, + // Ignorierte Verzeichnisse: CLI hat Vorrang, dann Config, sonst leer + ignored_dirs: match (!args.ignore.is_empty(), file_config.ignore) { + (true, _) => args.ignore, + (false, Some(set)) => set.into_iter().collect(), + (false, None) => vec![], + }, + folder_icon: "📁".to_string(), + file_icon: "📄".to_string(), + align_comments: args.align_comments, + }; + + let mut builder = TreeBuilder::new(config); + + let (stop_spinner, spinner_handle) = if !args.quiet { + let (s, h) = start_spinner(8); + (Some(s), Some(h)) + } else { + (None, None) + }; + + let mut output = builder.build_tree().join("\n"); + + if builder.config.align_comments { + let lines = output.lines().map(String::from).collect::>(); + output = builder.align_lines_with_comments(&lines).join("\n"); + } + + if let Some(stop) = stop_spinner { + let _ = stop.send(()); + } + if let Some(handle) = spinner_handle { + let _ = handle.join(); + } + + let viewonly = args.viewonly || file_config.viewonly.unwrap_or(false); + let output_path = args.output.clone().or_else(|| file_config.output.map(Into::into)).unwrap_or_else(|| "tree.txt".into()); + + if !viewonly { + if let Err(e) = fs::write(&output_path, &output) { + eprintln!("Fehler beim Schreiben der Datei: {e}"); + std::process::exit(1); + } + if !args.quiet { + let (folders, files) = builder.stats(); + println!( + "Erfasst wurden {} Ordner und {} Dateien. Ergebnis in {} gespeichert.", + folders, + files, + output_path.display() + ); + view_timer(&timer); + } + } else { + println!("{}", output); + view_timer(&timer); + } } diff --git a/src/utils/ascii_spinner.rs b/src/utils/ascii_spinner.rs new file mode 100644 index 0000000..8269962 --- /dev/null +++ b/src/utils/ascii_spinner.rs @@ -0,0 +1,38 @@ +use std::sync::mpsc::{self, Sender}; +use std::thread::{self, JoinHandle}; +use std::time::Duration; +use std::io::Write; + +/// Startet einen minimalistischen ASCII-Spinner in einem Hintergrundthread. +/// +/// Gibt alle X Millisekunden einen neuen Frame auf stdout aus (mit `\r`). +/// Die Frequenz wird über `ticks_per_second` gesteuert. +/// +/// # Beispiel +/// ```no_run +/// # use treescanner::utils::ascii_spinner; +/// let (stop, handle) = ascii_spinner::start_spinner(8); // 8 Ticks pro Sekunde +/// let _ = stop.send(()); +/// let _ = handle.join(); +/// ``` +pub fn start_spinner(ticks_per_second: u64) -> (Sender<()>, JoinHandle<()>) { + let (tx, rx) = mpsc::channel(); + let frames = vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + let clamped = ticks_per_second.clamp(1, 20); + let interval = Duration::from_millis(1000 / clamped); + + let handle = thread::spawn(move || { + let mut idx = 0; + while rx.try_recv().is_err() { + print!("\r[{}] läuft ...", frames[idx % frames.len()]); + let _ = std::io::stdout().flush(); + idx += 1; + thread::sleep(interval); + } + // Spinnerzeile zuverlässig löschen + print!("\rr\x1B[2K\r"); // ANSI: ganze Zeile löschen + let _ = std::io::stdout().flush(); + }); + + (tx, handle) +} diff --git a/src/utils/logger.rs b/src/utils/logger.rs index f314922..8cb9bef 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -1,2 +1,2 @@ -pub fn init() { +pub fn init_logger() { } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d991728..a15be27 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ pub mod logger; +pub mod ascii_spinner; diff --git a/tests/config_tests.rs b/tests/config_tests.rs new file mode 100644 index 0000000..5f14a4c --- /dev/null +++ b/tests/config_tests.rs @@ -0,0 +1,54 @@ +use treescanner::config::loader::{load_config_from_home, ConfigFile}; +use std::fs; +use std::path::PathBuf; +use std::fs::write; + +fn write_temp_config(content: &str) -> PathBuf { + let path = dirs::home_dir().unwrap().join(".treescanner.conf"); + fs::write(&path, content).expect("Konnte Testdatei nicht schreiben"); + path +} + +#[test] +fn test_config_wird_korrekt_geladen() { + let config_text = r#" + max_depth = 2 + max_files_per_dir = 5 + ignore = [".git", "target"] + viewonly = true + "#; + + let conf_path = write_temp_config(config_text); + let loaded: ConfigFile = load_config_from_home().expect("Konfig konnte nicht geladen werden"); + + assert_eq!(loaded.max_depth, Some(2)); + assert_eq!(loaded.max_files_per_dir, Some(5)); + assert!(loaded.ignore.as_ref().unwrap().contains(".git")); + assert_eq!(loaded.viewonly, Some(true)); + + fs::remove_file(conf_path).ok(); +} + +/// Test: `ignore` aus Konfigurationsdatei wird korrekt geladen +#[test] +fn test_ignore_from_config_file() { + let temp_home = tempfile::tempdir().unwrap(); + let config_path = temp_home.path().join(".treescanner.conf"); + + let config_content = r#" +ignore = [".git", "target", ".vscode"] +"#; + write(&config_path, config_content).expect("Konnte temporäre Config nicht schreiben."); + + unsafe { + std::env::set_var("HOME", temp_home.path()); // für Unix + std::env::set_var("USERPROFILE", temp_home.path()); // für Windows + } + + let config = load_config_from_home().expect("Konfig konnte nicht geladen werden."); + let ignore_set = config.ignore.expect("ignore-Feld fehlt in Config"); + + assert!(ignore_set.contains(".git")); + assert!(ignore_set.contains("target")); + assert!(ignore_set.contains(".vscode")); +} \ No newline at end of file diff --git a/tests/treebuilder_tests.rs b/tests/treebuilder_tests.rs new file mode 100644 index 0000000..c8cbbf2 --- /dev/null +++ b/tests/treebuilder_tests.rs @@ -0,0 +1,35 @@ +#[cfg(test)] +mod tests { + use treescanner::app::treebuilder::{TreeBuilder, TreeBuilderConfig}; + use std::path::PathBuf; + + fn mock_tree_builder() -> TreeBuilder { + let config = TreeBuilderConfig { + root_path: PathBuf::from("/mock"), + max_depth: Some(1), + max_files_per_dir: 3, + ignored_dirs: vec![], + folder_icon: "📁".to_string(), + file_icon: "📄".to_string(), + align_comments: true, + }; + TreeBuilder::new(config) + } + + #[test] + fn test_align_lines_with_comments() { + let builder = mock_tree_builder(); + let lines = vec![ + "📁 src/".to_string(), + "├── 📄 main.rs".to_string(), + "└── 📄 lib.rs".to_string(), + ]; + + let aligned = builder.align_lines_with_comments(&lines); + + // Prüfe, ob jede Zeile mit einem # endet + for line in aligned { + assert!(line.trim_end().ends_with('#'), "Fehlendes #: {}", line); + } + } +}