From 0652a34486965abb043890e7e9200c434d0f1ab6 Mon Sep 17 00:00:00 2001 From: Adam Skotarczak Date: Sat, 17 May 2025 21:02:24 +0200 Subject: [PATCH] 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