From f13829f14dd7f73da603a4cdb9a915cc3e33c323 Mon Sep 17 00:00:00 2001 From: Adam Skotarczak Date: Fri, 16 May 2025 13:51:03 +0200 Subject: [PATCH 1/4] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lauffähige Version in Entwicklung --- .gitignore | 5 +- Cargo.lock | 312 ++++++++++++++++++++++++++- Cargo.toml | 10 + LICENSE | 0 README.md | 24 +++ resources/.treescanner.conf.template | 7 + src/app/mod.rs | 4 +- src/app/treebuilder.rs | 98 +++++++++ src/config/args.rs | 44 ++++ src/config/loader.rs | 27 +++ src/config/mod.rs | 2 + src/main.rs | 68 +++++- 12 files changed, 581 insertions(+), 20 deletions(-) create mode 100644 LICENSE create mode 100644 resources/.treescanner.conf.template create mode 100644 src/app/treebuilder.rs create mode 100644 src/config/args.rs create mode 100644 src/config/loader.rs create mode 100644 src/config/mod.rs diff --git a/.gitignore b/.gitignore index 0104787..7a00c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ target/ # 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 +#.idea/ + +# Build - Test: +*.txt \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a6639cb..4751508 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,29 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[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 = "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 +193,46 @@ 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 = "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 +251,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -133,6 +312,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 +329,26 @@ dependencies = [ "unicode-ident", ] +[[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" @@ -189,7 +394,11 @@ checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" name = "treescanner" version = "0.1.0" dependencies = [ + "clap", + "dirs", "embed-resource", + "serde", + "toml", ] [[package]] @@ -198,6 +407,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 +433,28 @@ 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 = "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 +463,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 +494,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 +594,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] diff --git a/Cargo.toml b/Cargo.toml index 8a6f2fa..8642162 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,15 @@ name = "treescanner" version = "0.1.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" + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 8a87781..f32c4b8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,30 @@ Dieses Tool ist im Rahmen meines persönlichen Projekts von C nach Rust zu wechs ```plaintext +``` + +**GEPLANTE STRUKTUR (DEV)** + +```plaintext + +src/ +├── main.rs → CLI-Einstieg +├── app/ +│ ├── mod.rs +│ └── treebuilder.rs → Feature 1: Verzeichnisbaum +├── config/ +│ ├── mod.rs +│ └── args.rs → Feature 2: Parameterübergabe +├── output/ +│ └── writer.rs → Datei schreiben +├── formatting/ +│ └── aligner.rs → Ausrichtung Kommentare +├── i18n/ +│ └── messages.rs → Sprachausgabe / Lokalisierung +├── utils/ +│ ├── mod.rs +│ └── logger.rs + ``` --- diff --git a/resources/.treescanner.conf.template b/resources/.treescanner.conf.template new file mode 100644 index 0000000..d242ddf --- /dev/null +++ b/resources/.treescanner.conf.template @@ -0,0 +1,7 @@ +max_depth = 3 +max_files_per_dir = 50 +ignore = .git,__pycache__,target +language = de +align_comments = true +output = tree.txt +viewonly = false 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..6e54242 --- /dev/null +++ b/src/app/treebuilder.rs @@ -0,0 +1,98 @@ +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, +} + +/// Verantwortlich für das Erzeugen der ASCII-Baumstruktur. +pub struct TreeBuilder { + 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) -> String { + let mut lines = vec![format!("{} {}/", self.config.folder_icon, self.config.root_path.display())]; + self.scan_dir(&self.config.root_path.clone(), 0, "", &mut lines); + lines.join("\n") + } + + 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) + } +} diff --git a/src/config/args.rs b/src/config/args.rs new file mode 100644 index 0000000..0ae9a3a --- /dev/null +++ b/src/config/args.rs @@ -0,0 +1,44 @@ +use clap::Parser; +use std::path::PathBuf; + +/// Struktur für Kommandozeilenargumente mit Clap. +#[derive(Parser, Debug)] +#[command( + name = "TreeScanner", + author = "Adam Skotarczak ", + version, + about = "Generiert eine ASCII-Baumstruktur eines Verzeichnisses." +)] +pub struct CliArgs { + /// Stammverzeichnis (default: aktuelles Verzeichnis) + #[arg(default_value = ".")] + pub root_path: PathBuf, + + /// Maximale Anzahl Dateien pro Verzeichnis + #[arg(short = 'n', long, default_value_t = 100)] + pub max_files_per_dir: usize, + + /// Maximale Rekursionstiefe (optional) + #[arg(short = 'd', long)] + pub max_depth: Option, + + /// Keine Kommentar-Ausrichtung aktivieren (Zukunft) + #[arg(long)] + pub no_align_comments: bool, + + /// Sprache der Programmausgabe (de oder en) + #[arg(short = 'l', long, default_value = "de")] + pub language: String, + + /// Ignorierte Verzeichnisse (mehrfach möglich) + #[arg(short = 'x', long, value_name = "DIR", num_args = 0..)] + pub ignore: Vec, + + /// Pfad zur Ausgabedatei (Default: tree.txt) + #[arg(short = 'o', long)] + pub output: Option, + + /// Keine Dateiausgabe, nur Konsole + #[arg(long)] + pub viewonly: bool, +} diff --git a/src/config/loader.rs b/src/config/loader.rs new file mode 100644 index 0000000..5ca8f8c --- /dev/null +++ b/src/config/loader.rs @@ -0,0 +1,27 @@ +use std::collections::HashSet; +use std::fs; + +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, +} + +pub fn load_config_from_home() -> Option { + let home = dirs::home_dir()?; + let config_path = home.join(".treescanner.conf"); + if !config_path.exists() { + return None; + } + + let content = fs::read_to_string(&config_path).ok()?; + toml::from_str(&content).ok() +} 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/main.rs b/src/main.rs index 2252402..9e7de4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,64 @@ - -// Importiert das Modul `utils`, das vermutlich Unterfunktionen wie Fenster oder Logging enthält -mod utils; +mod config; mod app; +use app::treebuilder::{TreeBuilder, TreeBuilderConfig}; +use config::args::CliArgs; +use config::loader::load_config_from_home; +use clap::Parser; +use std::fs; + fn main() { - // Initialisiert das Logger-System (für Debug- oder Datei-Logging - nicht implementiert) - utils::logger::init(); - app::run(); -} + let args = CliArgs::parse(); + let file_config = load_config_from_home().unwrap_or_default(); + + 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) + }, + ignored_dirs: if !args.ignore.is_empty() { + args.ignore + } else { + file_config.ignore.unwrap_or_default().into_iter().collect() + }, + folder_icon: "📁".to_string(), + file_icon: "📄".to_string(), + }; + + let mut builder = TreeBuilder::new(config); + let output = builder.build_tree(); + + 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); + } + let (folders, files) = builder.stats(); + println!( + "Erfasst wurden {} Ordner und {} Dateien. Ergebnis in {} gespeichert.", + folders, + files, + output_path.display() + ); + } else { + println!("{}", output); + } +} From 1210da7601aae6a7874cc8960caaedd4dc82a074 Mon Sep 17 00:00:00 2001 From: Adam Skotarczak Date: Sat, 17 May 2025 00:38:45 +0200 Subject: [PATCH 2/4] wip - funktionierende Version --- .gitignore | 16 ++++++---------- .vscode/tasks.json | 32 ++++++++++++++++++++++++++++++++ src/config/args.rs | 8 ++++++++ src/lib.rs | 3 +++ src/main.rs | 35 ++++++++++++++++++++++++++++------- src/utils/ascii_spinner.rs | 35 +++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 1 + tests/config_tests.rs | 29 +++++++++++++++++++++++++++++ 8 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 .vscode/tasks.json create mode 100644 src/lib.rs create mode 100644 src/utils/ascii_spinner.rs create mode 100644 tests/config_tests.rs diff --git a/.gitignore b/.gitignore index 7a00c3f..5acf9c9 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,12 +9,8 @@ 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/ - # Build - Test: -*.txt \ No newline at end of file +*.txt + +# Releases (realAscot) +releases/** 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/src/config/args.rs b/src/config/args.rs index 0ae9a3a..831bafb 100644 --- a/src/config/args.rs +++ b/src/config/args.rs @@ -10,6 +10,14 @@ use std::path::PathBuf; about = "Generiert eine ASCII-Baumstruktur eines Verzeichnisses." )] pub struct CliArgs { + /// Aktiviere den Debug-Modus + #[clap(short = 'D', long = "debug", global = true, action)] + pub debug: bool, + + /// Aktiviere den Silent-Mode für Verwendung in Batch und Skripten + #[clap(short = 'q', long, global = true, action)] + pub quiet: bool, + /// Stammverzeichnis (default: aktuelles Verzeichnis) #[arg(default_value = ".")] pub root_path: PathBuf, 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 9e7de4d..fd1db1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,11 +6,16 @@ use config::args::CliArgs; use config::loader::load_config_from_home; use clap::Parser; use std::fs; +use std::time::Instant; + +use treescanner::utils::ascii_spinner::start_spinner; fn main() { let args = CliArgs::parse(); let file_config = load_config_from_home().unwrap_or_default(); + let start_time = Instant::now(); + if !args.root_path.is_dir() { eprintln!("Fehler: '{}' ist kein gültiges Verzeichnis.", args.root_path.display()); std::process::exit(1); @@ -41,8 +46,21 @@ fn main() { }; let mut builder = TreeBuilder::new(config); + + // Spinner starten, wenn nicht quiet + let spinner = if !args.quiet { + Some(start_spinner(2)) + } else { + None + }; + let output = builder.build_tree(); + if let Some(stop) = spinner { + let _ = stop.send(()); + } + println!(); // saubere Zeile nach Spinner + 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()); @@ -51,13 +69,16 @@ fn main() { eprintln!("Fehler beim Schreiben der Datei: {e}"); std::process::exit(1); } - let (folders, files) = builder.stats(); - println!( - "Erfasst wurden {} Ordner und {} Dateien. Ergebnis in {} gespeichert.", - folders, - files, - output_path.display() - ); + if !args.quiet { + let (folders, files) = builder.stats(); + println!( + "Erfasst wurden {} Ordner und {} Dateien. Ergebnis in {} gespeichert.", + folders, + files, + output_path.display() + ); + println!("⏱️ Gesamtlaufzeit: {:.2?}", start_time.elapsed()); + } } else { println!("{}", output); } diff --git a/src/utils/ascii_spinner.rs b/src/utils/ascii_spinner.rs new file mode 100644 index 0000000..cfb41cf --- /dev/null +++ b/src/utils/ascii_spinner.rs @@ -0,0 +1,35 @@ +use std::sync::mpsc::{self, Sender}; +use std::thread; +use std::time::Duration; + +/// 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 +/// ``` +/// let stop = ascii_spinner::start_spinner(8); // 8 Ticks pro Sekunde +/// // ... lange Operation ... +/// let _ = stop.send(()); +/// ``` +pub fn start_spinner(ticks_per_second: u64) -> Sender<()> { + let (tx, rx) = mpsc::channel(); + let frames = vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + let clamped = ticks_per_second.clamp(1, 20); + let interval = Duration::from_millis(1000 / clamped); + + thread::spawn(move || { + let mut idx = 0; + while rx.try_recv().is_err() { + print!("\r[{}] läuft ...", frames[idx % frames.len()]); + let _ = std::io::Write::flush(&mut std::io::stdout()); + idx += 1; + thread::sleep(interval); + } + print!("\r \r"); // Spinner löschen + let _ = std::io::Write::flush(&mut std::io::stdout()); + }); + + tx +} 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..ce7996e --- /dev/null +++ b/tests/config_tests.rs @@ -0,0 +1,29 @@ +use treescanner::config::loader::{load_config_from_home, ConfigFile}; +use std::fs; +use std::path::PathBuf; + +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(); +} From d683594d6a526e96bf50b6793b79d0652c85a897 Mon Sep 17 00:00:00 2001 From: Adam Skotarczak Date: Sat, 17 May 2025 13:23:04 +0200 Subject: [PATCH 3/4] preRelease MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vorerst alle geplanten Features Implementiert. - Tests stehen noch aus und Schönheitskorrekturen geplant --- LICENSE | 26 +++++++++++++ README.md | 76 +++++++++++++++++++++++++++----------- src/app/treebuilder.rs | 24 +++++++++--- src/config/args.rs | 75 ++++++++++++++++++++----------------- src/main.rs | 39 +++++++++++++------ src/utils/ascii_spinner.rs | 25 +++++++------ src/utils/logger.rs | 2 +- tests/treebuilder_tests.rs | 35 ++++++++++++++++++ 8 files changed, 218 insertions(+), 84 deletions(-) create mode 100644 tests/treebuilder_tests.rs diff --git a/LICENSE b/LICENSE index e69de29..e68ca1a 100644 --- a/LICENSE +++ 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/README.md b/README.md index f32c4b8..4440ff9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ ![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 CLI-Tool zur Darstellung von Verzeichnisstrukturen als ASCII-Baum. Dieses Tool entstand im Rahmen meines persönlichen Projekts, systemnahe Werkzeuge von C nach Rust zu migrieren. + +Der original treeScanner in Python ist unter zu finden. Dieser ist auch als Python-Modul zu verwenden. + +--- ## Inhalt @@ -10,49 +14,79 @@ Dieses Tool ist im Rahmen meines persönlichen Projekts von C nach Rust zu wechs - [Inhalt](#inhalt) - [Struktur](#struktur) - [Features](#features) + - [Verwendung](#verwendung) + - [Beispielausgabe](#beispielausgabe) - [Lizenz](#lizenz) --- ## Struktur -**Diese Struktur ist eine Ausgabe des Tools:** - -```plaintext - - -``` - **GEPLANTE STRUKTUR (DEV)** ```plaintext - src/ ├── main.rs → CLI-Einstieg ├── app/ │ ├── mod.rs -│ └── treebuilder.rs → Feature 1: Verzeichnisbaum +│ └── treebuilder.rs → Verzeichnisbaum erstellen ├── config/ -│ ├── mod.rs -│ └── args.rs → Feature 2: Parameterübergabe -├── output/ -│ └── writer.rs → Datei schreiben -├── formatting/ -│ └── aligner.rs → Ausrichtung Kommentare -├── i18n/ -│ └── messages.rs → Sprachausgabe / Lokalisierung +│ └── args.rs → Parameterübergabe & Konfig ├── utils/ -│ ├── mod.rs +│ ├── ascii_spinner.rs → Fortschrittsanzeige │ └── logger.rs - +├── tests/ → Integrationstests +├── media/ → Logos / Assets +├── resources/ → .conf-Template, Icons, Versioninfo ``` --- ## Features +- 📁 ASCII-Baumstruktur mit Icons (📁, 📄) +- 📂 Max. Tiefe & Datei-Anzahl konfigurierbar (`--max-depth`, `--max-files-per-dir`) +- 🚫 Ignorieren von Verzeichnissen (`--ignore .git,target`) +- 💬 Optional ausrichtbare Kommentarspalte (`--align-comments`) +- ⚙ Konfigurierbar per CLI oder `~/.treescanner.conf` +- 🌀 Fortschrittsanzeige während des Scans +- 🛠 `--quiet`, `--debug`, `--viewonly`, `--output` u. a. +- 🧪 Tests, strukturierter Build, Markdown-fähige Ausgabe + +--- + +## 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 # +``` + --- ## Lizenz ---- +MIT © [Adam Skotarczak](mailto:adam@skotarczak.net) siehe [LICENSE](./LICENSE) diff --git a/src/app/treebuilder.rs b/src/app/treebuilder.rs index 6e54242..9004b9d 100644 --- a/src/app/treebuilder.rs +++ b/src/app/treebuilder.rs @@ -9,11 +9,12 @@ pub struct TreeBuilderConfig { 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 { - config: TreeBuilderConfig, + pub config: TreeBuilderConfig, folder_count: usize, file_count: usize, } @@ -28,10 +29,11 @@ impl TreeBuilder { } /// Startet den Scan und liefert das Ergebnis als String. - pub fn build_tree(&mut self) -> String { - let mut lines = vec![format!("{} {}/", self.config.folder_icon, self.config.root_path.display())]; - self.scan_dir(&self.config.root_path.clone(), 0, "", &mut lines); - lines.join("\n") + 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) { @@ -95,4 +97,16 @@ impl TreeBuilder { 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 index 831bafb..020da0f 100644 --- a/src/config/args.rs +++ b/src/config/args.rs @@ -1,52 +1,59 @@ use clap::Parser; use std::path::PathBuf; -/// Struktur für Kommandozeilenargumente mit Clap. +/// CLI-Argumente für TreeScanner #[derive(Parser, Debug)] -#[command( - name = "TreeScanner", - author = "Adam Skotarczak ", - version, - about = "Generiert eine ASCII-Baumstruktur eines Verzeichnisses." +#[command( author ="Adam Skotarczak ", + version= "1.0.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 { - /// Aktiviere den Debug-Modus - #[clap(short = 'D', long = "debug", global = true, action)] - pub debug: bool, - - /// Aktiviere den Silent-Mode für Verwendung in Batch und Skripten - #[clap(short = 'q', long, global = true, action)] - pub quiet: bool, - - /// Stammverzeichnis (default: aktuelles Verzeichnis) + /// Root-Verzeichnis für den Scan (Standard: aktuelles Verzeichnis) #[arg(default_value = ".")] pub root_path: PathBuf, - /// Maximale Anzahl Dateien pro Verzeichnis - #[arg(short = 'n', long, default_value_t = 100)] - pub max_files_per_dir: usize, - - /// Maximale Rekursionstiefe (optional) - #[arg(short = 'd', long)] + /// Maximale Scan-Tiefe + #[arg(long)] pub max_depth: Option, - /// Keine Kommentar-Ausrichtung aktivieren (Zukunft) - #[arg(long)] - pub no_align_comments: bool, + /// Maximale Dateianzahl pro Verzeichnis (Standard: 100) + #[arg(long, default_value_t = 100)] + pub max_files_per_dir: usize, - /// Sprache der Programmausgabe (de oder en) - #[arg(short = 'l', long, default_value = "de")] - pub language: String, - - /// Ignorierte Verzeichnisse (mehrfach möglich) - #[arg(short = 'x', long, value_name = "DIR", num_args = 0..)] + /// Verzeichnisse ignorieren (z. B. .git,target) durch Komma getrennt, ohne Leerzeichen. + #[arg(short = 'x', long, value_delimiter = ',')] pub ignore: Vec, - /// Pfad zur Ausgabedatei (Default: tree.txt) - #[arg(short = 'o', long)] + /// Ausgabeziel (Standard: tree.txt) + #[arg(short, long)] pub output: Option, - /// Keine Dateiausgabe, nur Konsole + /// 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(long, default_value_t = false)] + pub align_comments: bool, +} diff --git a/src/main.rs b/src/main.rs index fd1db1b..7aba035 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,26 @@ 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; -use treescanner::utils::ascii_spinner::start_spinner; +/// Gibt die verstrichene Zeit seit `timer` aus. +fn view_timer(timer: &Instant) { + println!("\n⏱️ Gesamtlaufzeit des Scans: {:.2?}", timer.elapsed()); +} fn main() { + init_logger(); let args = CliArgs::parse(); let file_config = load_config_from_home().unwrap_or_default(); - - let start_time = Instant::now(); + let timer = Instant::now(); if !args.root_path.is_dir() { eprintln!("Fehler: '{}' ist kein gültiges Verzeichnis.", args.root_path.display()); @@ -43,23 +49,31 @@ fn main() { }, folder_icon: "📁".to_string(), file_icon: "📄".to_string(), + align_comments: args.align_comments, }; let mut builder = TreeBuilder::new(config); - // Spinner starten, wenn nicht quiet - let spinner = if !args.quiet { - Some(start_spinner(2)) + let (stop_spinner, spinner_handle) = if !args.quiet { + let (s, h) = start_spinner(8); + (Some(s), Some(h)) } else { - None + (None, None) }; - let output = builder.build_tree(); + let mut output = builder.build_tree().join("\n"); - if let Some(stop) = spinner { + 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(()); } - println!(); // saubere Zeile nach Spinner + 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()); @@ -77,9 +91,10 @@ fn main() { files, output_path.display() ); - println!("⏱️ Gesamtlaufzeit: {:.2?}", start_time.elapsed()); + view_timer(&timer); } } else { println!("{}", output); + view_timer(&timer); } -} +} diff --git a/src/utils/ascii_spinner.rs b/src/utils/ascii_spinner.rs index cfb41cf..8269962 100644 --- a/src/utils/ascii_spinner.rs +++ b/src/utils/ascii_spinner.rs @@ -1,6 +1,7 @@ use std::sync::mpsc::{self, Sender}; -use std::thread; +use std::thread::{self, JoinHandle}; use std::time::Duration; +use std::io::Write; /// Startet einen minimalistischen ASCII-Spinner in einem Hintergrundthread. /// @@ -8,28 +9,30 @@ use std::time::Duration; /// Die Frequenz wird über `ticks_per_second` gesteuert. /// /// # Beispiel -/// ``` -/// let stop = ascii_spinner::start_spinner(8); // 8 Ticks pro Sekunde -/// // ... lange Operation ... +/// ```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<()> { +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); - thread::spawn(move || { + 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::Write::flush(&mut std::io::stdout()); + let _ = std::io::stdout().flush(); idx += 1; thread::sleep(interval); } - print!("\r \r"); // Spinner löschen - let _ = std::io::Write::flush(&mut std::io::stdout()); + // Spinnerzeile zuverlässig löschen + print!("\rr\x1B[2K\r"); // ANSI: ganze Zeile löschen + let _ = std::io::stdout().flush(); }); - tx -} + (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/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); + } + } +} From 0652a34486965abb043890e7e9200c434d0f1ab6 Mon Sep 17 00:00:00 2001 From: Adam Skotarczak Date: Sat, 17 May 2025 21:02:24 +0200 Subject: [PATCH 4/4] ready2merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - voll funktionsfähige Version - kann auf GitHub hochgeladen werden --- .gitignore | 4 + CHANGELOG.md | 27 +++++ Cargo.lock | 91 +++++++++++++- Cargo.toml | 4 +- Makefile | 43 +++++++ README.md | 175 ++++++++++++++++++++++----- VERSION | 2 +- desktop.ini | 6 + resources/.treescanner.conf.template | 7 -- src/config/args.rs | 8 +- src/config/loader.rs | 72 ++++++++++- src/main.rs | 13 +- tests/config_tests.rs | 25 ++++ 13 files changed, 420 insertions(+), 57 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 Makefile create mode 100644 desktop.ini delete mode 100644 resources/.treescanner.conf.template diff --git a/.gitignore b/.gitignore index 5acf9c9..582e1c2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,9 @@ target/** # Build - Test: *.txt +# Develop +TODO.md + # Releases (realAscot) releases/** +bin/** 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 4751508..4a27a14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,22 @@ 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" @@ -168,7 +184,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "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]] @@ -215,6 +243,12 @@ dependencies = [ "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" @@ -251,13 +285,19 @@ 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", + "getrandom 0.2.16", "libredox", "thiserror", ] @@ -271,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" @@ -329,6 +382,19 @@ 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" @@ -392,12 +458,13 @@ checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" [[package]] name = "treescanner" -version = "0.1.0" +version = "1.0.0" dependencies = [ "clap", "dirs", "embed-resource", "serde", + "tempfile", "toml", ] @@ -439,6 +506,15 @@ 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" @@ -596,3 +672,12 @@ dependencies = [ "cfg-if", "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 8642162..04621a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "treescanner" -version = "0.1.0" +version = "1.0.0" edition = "2024" [package.metadata.winres] @@ -15,3 +15,5 @@ serde = { version = "1.0", features = ["derive"] } toml = "0.8" dirs = "5" +[dev-dependencies] +tempfile = "3" 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 4440ff9..341ba32 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ ![TreeScanner-Logo](./media/logo-treescanner_512x512.png) -TreeScanner ist ein leichtgewichtiges CLI-Tool zur Darstellung von Verzeichnisstrukturen als ASCII-Baum. Dieses Tool entstand im Rahmen meines persönlichen Projekts, systemnahe Werkzeuge von C nach Rust zu migrieren. +**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. @@ -12,11 +16,40 @@ Der original treeScanner in Python ist unter \.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 -MIT © [Adam Skotarczak](mailto:adam@skotarczak.net) siehe [LICENSE](./LICENSE) +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/resources/.treescanner.conf.template b/resources/.treescanner.conf.template deleted file mode 100644 index d242ddf..0000000 --- a/resources/.treescanner.conf.template +++ /dev/null @@ -1,7 +0,0 @@ -max_depth = 3 -max_files_per_dir = 50 -ignore = .git,__pycache__,target -language = de -align_comments = true -output = tree.txt -viewonly = false diff --git a/src/config/args.rs b/src/config/args.rs index 020da0f..14e023c 100644 --- a/src/config/args.rs +++ b/src/config/args.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; /// CLI-Argumente für TreeScanner #[derive(Parser, Debug)] #[command( author ="Adam Skotarczak ", - version= "1.0.0", + version= "0.2.0", about = "TreeScanner: Verzeichnisse als ASCII-Baum visualisieren.", long_about = r#" @@ -29,7 +29,7 @@ pub struct CliArgs { #[arg(long)] pub max_depth: Option, - /// Maximale Dateianzahl pro Verzeichnis (Standard: 100) + /// Maximale Dateianzahl pro Verzeichnis #[arg(long, default_value_t = 100)] pub max_files_per_dir: usize, @@ -38,7 +38,7 @@ pub struct CliArgs { pub ignore: Vec, /// Ausgabeziel (Standard: tree.txt) - #[arg(short, long)] + #[arg(short = 'o', long)] pub output: Option, /// Nur in Konsole anzeigen, keine Ausgabedatei speichern @@ -54,6 +54,6 @@ pub struct CliArgs { pub quiet: bool, /// Kommentare ausrichten (DEV: optisch instabil) - #[arg(long, default_value_t = false)] + #[arg(short = 'c', long, default_value_t = false)] pub align_comments: bool, } diff --git a/src/config/loader.rs b/src/config/loader.rs index 5ca8f8c..8736226 100644 --- a/src/config/loader.rs +++ b/src/config/loader.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; use std::fs; +use std::io::Write; use serde::Deserialize; @@ -15,13 +16,72 @@ pub struct ConfigFile { 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"); - if !config_path.exists() { - return None; - } - let content = fs::read_to_string(&config_path).ok()?; - toml::from_str(&content).ok() -} + // 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/main.rs b/src/main.rs index 7aba035..880d265 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,13 +23,13 @@ fn main() { let timer = Instant::now(); if !args.root_path.is_dir() { - eprintln!("Fehler: '{}' ist kein gültiges Verzeichnis.", args.root_path.display()); + 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}"); + eprintln!("⚠️ Fehler beim Erstellen des Zielordners: {e}"); std::process::exit(1); } } @@ -42,10 +42,11 @@ fn main() { } else { file_config.max_files_per_dir.unwrap_or(100) }, - ignored_dirs: if !args.ignore.is_empty() { - args.ignore - } else { - file_config.ignore.unwrap_or_default().into_iter().collect() + // 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(), diff --git a/tests/config_tests.rs b/tests/config_tests.rs index ce7996e..5f14a4c 100644 --- a/tests/config_tests.rs +++ b/tests/config_tests.rs @@ -1,6 +1,7 @@ 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"); @@ -27,3 +28,27 @@ fn test_config_wird_korrekt_geladen() { 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