Compare commits

...

2 Commits

Author SHA1 Message Date
0652a34486
ready2merge
- voll funktionsfähige Version
- kann auf GitHub hochgeladen werden
2025-05-17 21:02:24 +02:00
d683594d6a
preRelease
- vorerst alle geplanten Features Implementiert.
- Tests stehen noch aus und Schönheitskorrekturen geplant
2025-05-17 13:23:04 +02:00
18 changed files with 610 additions and 113 deletions

4
.gitignore vendored
View File

@ -12,5 +12,9 @@ target/**
# Build - Test:
*.txt
# Develop
TODO.md
# Releases (realAscot)
releases/**
bin/**

27
CHANGELOG.md Normal file
View File

@ -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

91
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"

26
LICENSE
View File

@ -0,0 +1,26 @@
MIT License
Copyright (c) 2025 Adam Skotarczak <adam@skotarczak.net>
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.

43
Makefile Normal file
View File

@ -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"

205
README.md
View File

@ -2,57 +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 <https://github.com/realAscot/treeScannerASCII> 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:**
```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
📁 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\<Benutzername>\.treescanner.conf
Linux: /home/<user>/.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)

View File

@ -1 +1 @@
0.1.0
1.0.0

6
desktop.ini Normal file
View File

@ -0,0 +1,6 @@
[.ShellClassInfo]
IconResource=C:\Users\adam\Documents\Werkbank\Projekte\Projekte-Rust\treeScannerRust\resources\icon.ico,0
[ViewState]
Mode=
Vid=
FolderType=Documents

View File

@ -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

View File

@ -9,11 +9,12 @@ pub struct TreeBuilderConfig {
pub ignored_dirs: Vec<String>,
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<String> {
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<String>) {
@ -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<String> {
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()
}
}

View File

@ -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 <adam@skotarczak.net>",
version,
about = "Generiert eine ASCII-Baumstruktur eines Verzeichnisses."
#[command( author ="Adam Skotarczak <adam@skotarczak.net>",
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 {
/// 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<usize>,
/// Keine Kommentar-Ausrichtung aktivieren (Zukunft)
#[arg(long)]
pub no_align_comments: bool,
/// Maximale Dateianzahl pro Verzeichnis
#[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<String>,
/// Pfad zur Ausgabedatei (Default: tree.txt)
/// Ausgabeziel (Standard: tree.txt)
#[arg(short = 'o', long)]
pub output: Option<PathBuf>,
/// 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(short = 'c', long, default_value_t = false)]
pub align_comments: bool,
}

View File

@ -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<bool>,
}
/// 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<ConfigFile> {
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::<ConfigFile>(&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
}
}
}

View File

@ -1,29 +1,35 @@
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());
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);
}
}
@ -36,30 +42,39 @@ 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(),
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::<Vec<_>>();
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 +92,10 @@ fn main() {
files,
output_path.display()
);
println!("⏱️ Gesamtlaufzeit: {:.2?}", start_time.elapsed());
view_timer(&timer);
}
} else {
println!("{}", output);
view_timer(&timer);
}
}
}

View File

@ -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)
}

View File

@ -1,2 +1,2 @@
pub fn init() {
pub fn init_logger() {
}

View File

@ -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"));
}

View File

@ -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);
}
}
}