Compare commits
2 Commits
1210da7601
...
0652a34486
Author | SHA1 | Date | |
---|---|---|---|
0652a34486 | |||
d683594d6a |
4
.gitignore
vendored
4
.gitignore
vendored
@ -12,5 +12,9 @@ target/**
|
||||
# Build - Test:
|
||||
*.txt
|
||||
|
||||
# Develop
|
||||
TODO.md
|
||||
|
||||
# Releases (realAscot)
|
||||
releases/**
|
||||
bin/**
|
||||
|
27
CHANGELOG.md
Normal file
27
CHANGELOG.md
Normal 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
91
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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
26
LICENSE
@ -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
43
Makefile
Normal 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
205
README.md
@ -2,57 +2,208 @@
|
||||
|
||||

|
||||
|
||||
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)
|
||||
|
6
desktop.ini
Normal file
6
desktop.ini
Normal 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
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
52
src/main.rs
52
src/main.rs
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
pub fn init() {
|
||||
pub fn init_logger() {
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
35
tests/treebuilder_tests.rs
Normal file
35
tests/treebuilder_tests.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user