From bf1d77feb1966482429a3f5d093a11f357fefcc2 Mon Sep 17 00:00:00 2001 From: Adam Skotarczak Date: Sat, 26 Apr 2025 04:13:07 +0200 Subject: [PATCH] v0.1.0 (CHANGELOG) --- CHANGELOG.md | 29 ++++++-- README.md | 77 ++++++++++++++++------ scanner.py | 182 ++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 233 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9613e48..e0915ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,39 @@ # CHANGELOG -- 2025-04-25 - initial commit +- **2025-04-26 – Commit v0.1.0** - **Geändert:** - - [x] `treescanner.py` umgebaut zu kombinierter Modul- und Standalone-Version + - [x] `scan_directory()`: Platzhalter `` wird jetzt mit Datei-Icon und korrektem Connector (`├──`/`└──`) ausgegeben. + - [x] `main()`: CLI-Parameterunterstützung via `argparse` implementiert (`-h/--help`, `-n/--max-files-per-dir`, `-d/--max-depth`, `--no-align-comments`). + - [x] [README.md](./README.md) überarbeitet + + - **Hinzugefügt:** + - [x] Vollständige Google-Style Docstrings für alle Klassen, Methoden und Funktionen. + - [x] Umfangreiche Inline-Kommentare zur Erläuterung von Logik und Parametern. + + - **Geprüft:** + - [x] Ausgabe von `` mit Icon und Connector validiert. + - [x] CLI-Parameterhandling und Hilfe (`-h`) getestet. + - [x] Google-Style Docstrings in VS Code-Tooltips überprüft. + +--- + +- **2025-04-25 - initial commit** + + - **Geändert:** + - [x] `scanner.py` umgebaut zu kombinierter Modul- und Standalone-Version - [x] Konfigurationsklasse `TreeScannerConfig` eingebaut - [x] Klasse `TreeScanner` erstellt und bestehende Logik dorthin verschoben - **Hinzugefügt:** - - [x] `test_usage.py` als __Beispiel__ für modulare Verwendung + - [x] `test_usage.py` als **Beispiel** für modulare Verwendung - [x] `README.md` erstellt mit Anleitung für Standalone- und Modulnutzung (Template) - [x] `pyproject.toml` erstellt für spätere Paketinstallation mit PEP 621 - - [ ] `TODO.md` ist eingefügt aber wird vorerst noch nicht versioniert. + - [X] `TODO.md` ist eingefügt aber wird vorerst noch nicht versioniert. + - [x] `LICENSE.md` eingefügt und vorerst MIT Lizensiert. - **Geprüft:** - [x] `scanner.py` solo in ein Verzeichnis kopieren und ausführen mit `python scanner.py` - [x] Import und Nutzung als Modul aus `test_usage.py` + +--- diff --git a/README.md b/README.md index d24e6d8..e876e8c 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,83 @@ -# TreeScanner +# README for TreeScanner -Ein flexibler Verzeichnisscanner für die Kommandozeile **und** zur Einbindung als Python-Modul. +Ein flexibler Verzeichnisscanner für die Kommandozeile und zur Einbindung als Python-Modul. ## Projektstruktur ```plaintext - -treescanner/ -├── __init__.py -├── __main__.py # Standalone-Ausführung -├── scanner.py # Das eigentliche Modul mit Klasse + Logik -├── config.py # Konfigurationsklasse separat -└── test_usage.py # Beispielverwendung als Modul +📁 treescannerASCII/ # Projekt-Root +├── 📁 media # Bilder/Icons für GitHub, Ausgabe etc. +├── 📄 .gitignore # Ignorierte Dateien +├── 📄 CHANGELOG.md # Änderungsprotokoll (Markdown) +├── 📄 LICENSE # Lizenzdatei (MIT) +├── 📄 README.md # Diese Anleitung +├── 📄 TODO.md # Offene Aufgaben +├── 📄 __init__.py # Modul-Initialisierung +├── 📄 __main__.py # Einstiegspunkt für `python -m treescanner` +├── 📄 scanner.py # Hauptimplementierung +└── 📄 test_usage.py # Beispiel für Modul-Integration ``` -## 🔧 Verwendung als Standalone +## 🔧 Standalone-Ausführung (CLI) ```bash -python treescanner.py +python scanner.py [root_path] [-n N] [-d DEPTH] [--no-align-comments] [-h] ``` -Erzeugt eine Datei `tree.txt` mit der Verzeichnisstruktur ab dem aktuellen Pfad. +| Parameter | Beschreibung | +|-------------------------|---------------------------------------------------------------------------------------------------------------| +| `root_path` | Optionales Verzeichnis, ab dem gescannt wird (Default: aktueller Pfad). | +| `-n`, `--max-files-per-dir` | Begrenze die Anzahl an Dateien pro Verzeichnis (Default: 2). | +| `-d`, `--max-depth` | Maximale Tiefe der Rekursion; unbegrenzt, wenn nicht gesetzt. | +| `--no-align-comments` | Deaktiviert die Ausrichtung der Kommentar-Platzhalter am Zeilenende. | +| `-h`, `--help` | Zeigt diese Hilfe an und beendet das Programm. | + +Die Ausgabe wird in die Datei `tree.txt` geschrieben. ## 🧩 Verwendung als Modul ```python from treescanner import TreeScanner, TreeScannerConfig -config = TreeScannerConfig(root_path=".", max_depth=2) +# Konfiguration mit Pfad, Auswahl der Maximaltiefe und Ausrichtung +config = TreeScannerConfig( + root_path="./", # zu scannender Pfad + max_depth=3, # maximale Rekursionstiefe + max_files_per_dir=5, # bis zu 5 Dateien pro Ordner anzeigen + align_comments=True # Kommentare ausrichten +) scanner = TreeScanner(config) output = scanner.generate_tree() print(output) ``` -## ⚙️ Konfiguration +> Hinweis: Alle Klassen und Methoden sind mit **Google-Style Docstrings** versehen. Moderne IDEs (VS Code, PyCharm) zeigen so direkt Parameter und Rückgabetypen als Tooltip an. -Die `TreeScannerConfig`-Klasse erlaubt dir u. a.: +## ⚙️ Konfiguration via `TreeScannerConfig` -- `root_path`: Startverzeichnis -- `max_depth`: maximale Rekursionstiefe -- `max_files_per_dir`: wie viele Dateien pro Ordner angezeigt werden -- `align_comments`: Kommentar-Ausrichtung aktivieren -- `folder_icon` / `file_icon`: Anzeige-Icons +| Attribut | Typ | Beschreibung | +|-----------------------|-------------------|-------------------------------------------------------------------------| +| `root_path: str` | Pfad | Basisverzeichnis zum Scannen (Default: `.`) | +| `folder_icon: str` | Unicode-Zeichen | Symbol für Verzeichnisse (Default: 📁) | +| `file_icon: str` | Unicode-Zeichen | Symbol für Dateien und Platzhalter (Default: 📄) | +| `max_files_per_dir: int` | Ganzzahl | Maximale angezeigte Dateien pro Verzeichnis (Default: 2) | +| `max_depth: Optional[int]` | Ganzzahl/None | Maximale Rekursionstiefe, `None` = unlimitiert | +| `align_comments: bool` | Wahr/Falsch | Kommentare am Zeilenende ausrichten (Default: `True`) | + +## 📄 Beispielausgabe + +```plaintext +📁 treescannerASCII/ +├── 📁 media +│ ├── 📄 favicon.ico +│ ├── 📄 logo-bw-1024x1024.png +│ └── 📄 +├── 📄 .gitignore +├── 📄 CHANGELOG.md +├── 📄 README.md +└── 📄 +``` ## 📄 Lizenz -MIT (optional anpassen) +MIT – siehe [LICENSE](./LICENSE) diff --git a/scanner.py b/scanner.py index 93a58b9..52bf0ae 100644 --- a/scanner.py +++ b/scanner.py @@ -1,22 +1,50 @@ #!/usr/bin/env python3 - """ -Verzeichnisscanner mit strukturierter Ausgabe. -Funktioniert sowohl als Standalone-Skript als auch als einbindbares Modul. +TreeScanner: Verzeichnisbaum-Ausgabe mit ASCII-Art. + +Standalone-Ausführung unterstützt folgende CLI-Parameter: + root_path Pfad des Stammverzeichnisses (Default: aktuelles Verzeichnis). + -n, --max-files-per-dir Maximale Anzahl Dateien pro Verzeichnis (Default: 2). + -d, --max-depth Maximale Rekursionstiefe (Default: unbegrenzt). + --no-align-comments Deaktiviert das Ausrichten der Kommentare. + -h, --help Diese Hilfe anzeigen und Programm beenden. """ import os +import argparse from typing import Optional, List -# === Konfigurationsklasse === class TreeScannerConfig: - def __init__(self, - root_path: str = ".", - folder_icon: str = "\U0001F4C1", - file_icon: str = "\U0001F4C4", - max_files_per_dir: int = 100, - max_depth: Optional[int] = None, - align_comments: bool = True): # + """ + Konfigurationsklasse für den TreeScanner. + + Attributes: + root_path (str): Pfad des Stammverzeichnisses. + folder_icon (str): Icon für Ordner. + file_icon (str): Icon für Dateien und Platzhalter. + max_files_per_dir (int): Maximale Anzahl Dateien, die pro Verzeichnis angezeigt werden. + max_depth (Optional[int]): Maximale Tiefe der Rekursion (None = unbeschränkt). + align_comments (bool): Ob Kommentare am Zeilenende ausgerichtet werden sollen. + """ + + def __init__( + self, + root_path: str = ".", + folder_icon: str = "\U0001F4C1", + file_icon: str = "\U0001F4C4", + max_files_per_dir: int = 2, + max_depth: Optional[int] = None, + align_comments: bool = True, + ): + """ + Args: + root_path (str): Pfad des Stammverzeichnisses. + folder_icon (str): Icon für Ordner. + file_icon (str): Icon für Dateien und Platzhalter. + max_files_per_dir (int): Maximale Anzahl Dateien pro Verzeichnis. + max_depth (Optional[int]): Maximale Rekursionstiefe, None = unlimitiert. + align_comments (bool): Kommentare ausrichten, wenn True. + """ self.root_path = root_path self.folder_icon = folder_icon self.file_icon = file_icon @@ -24,63 +52,157 @@ class TreeScannerConfig: self.max_depth = max_depth self.align_comments = align_comments -# === Hauptklasse === class TreeScanner: + """ + Klasse zum Scannen von Verzeichnissen und Erzeugen einer ASCII-Baumstruktur. + """ + def __init__(self, config: TreeScannerConfig): + """ + Args: + config (TreeScannerConfig): Konfiguration für den Scanner. + """ self.config = config def scan_directory(self, path: str, depth: int = 0, prefix: str = "") -> List[str]: - lines = [] + """ + Scannt ein Verzeichnis und gibt eine Liste von ASCII-Zeilen zurück. + + Args: + path (str): Pfad des zu scannenden Verzeichnisses. + depth (int): Aktuelle Rekursionstiefe. + prefix (str): Präfix für Einrückung und Connectoren. + + Returns: + List[str]: Zeilen mit ASCII-Baumstruktur für dieses Verzeichnis. + """ + lines: List[str] = [] try: entries = sorted(os.listdir(path)) except PermissionError: + # Zugriff verweigert: Spezielle Rückgabe, kein Rekursionsaufruf return [f"{prefix}└── [Zugriff verweigert] {path}"] + # Aufteilen in Ordner und Dateien folders = [e for e in entries if os.path.isdir(os.path.join(path, e))] files = [e for e in entries if os.path.isfile(os.path.join(path, e))] + # 1) Verzeichnisse verarbeiten for idx, folder in enumerate(folders): folder_path = os.path.join(path, folder) + # ├──, wenn nicht letzter Ordner oder noch Dateien folgen, sonst └── connector = "├── " if idx < len(folders) - 1 or files else "└── " - line = f"{prefix}{connector}{self.config.folder_icon} {folder}" - lines.append(line) + lines.append(f"{prefix}{connector}{self.config.folder_icon} {folder}") + # Rekursive Ausgabe, falls max_depth nicht überschritten if self.config.max_depth is None or depth < self.config.max_depth: + # Verlängerung des Präfixes: '│ ' oder Leerraum extension = "│ " if idx < len(folders) - 1 or files else " " lines.extend(self.scan_directory(folder_path, depth + 1, prefix + extension)) - for idx, file in enumerate(files[:self.config.max_files_per_dir]): - connector = "├── " if idx < len(files[:self.config.max_files_per_dir]) - 1 else "└── " - line = f"{prefix}{connector}{self.config.file_icon} {file}" - lines.append(line) + # 2) Dateien plus Platzhalter als kombinierte Liste behandeln + visible_files = files[: self.config.max_files_per_dir] + remaining = len(files) - len(visible_files) - if len(files) > self.config.max_files_per_dir: - remaining = len(files) - self.config.max_files_per_dir - lines.append(f"{prefix}└── ") + # Kombinieren: echte Dateien + Platzhalter + combined = visible_files.copy() + if remaining > 0: + combined.append(f"") + + # Ausgabe mit Datei-Icon für echte Dateien UND Platzhalter + for idx, name in enumerate(combined): + # ├── für alle außer dem letzten Eintrag, sonst └── + connector = "├── " if idx < len(combined) - 1 else "└── " + # Icon hinzufügen (auch beim Platzhalter) + lines.append(f"{prefix}{connector}{self.config.file_icon} {name}") return lines def align_lines_with_comments(self, lines: List[str]) -> List[str]: + """ + Richtet alle Zeilen so aus, dass Kommentare am gleichen Spaltenindex stehen. + + Args: + lines (List[str]): Liste der Baumstruktur-Zeilen ohne Kommentare. + + Returns: + List[str]: Zeilen mit ausgerichteten Kommentar-Platzhaltern (#). + """ + # Bestimme Länge der längsten Zeile (ohne Trailing Spaces) max_length = max(len(line.rstrip()) for line in lines) - return [ - line.rstrip() + (" " * (max_length - len(line.rstrip()) + 2)) + "# " - for line in lines - ] + aligned: List[str] = [] + for line in lines: + text = line.rstrip() + # Padding-Breite: definierte Spaltenausrichtung (+2 Leerzeichen) + padding = " " * (max_length - len(text) + 2) + aligned.append(text + padding + "# ") + return aligned def generate_tree(self) -> str: + """ + Generiert den vollständigen Verzeichnisbaum als String. + + Returns: + str: Mehrzeiliger String mit Ordner-Icon, Ordner- und Dateienstruktur. + """ + # Stammverzeichnisname ermitteln root_name = os.path.basename(os.path.abspath(self.config.root_path)) or self.config.root_path lines = [f"{self.config.folder_icon} {root_name}/"] + # Baumstruktur dahinter generieren lines += self.scan_directory(self.config.root_path) - + # Optional Kommentare ausrichten if self.config.align_comments: lines = self.align_lines_with_comments(lines) - return "\n".join(lines) -# === Standalone-Ausführung === def main(): - config = TreeScannerConfig() + """ + Standalone-Ausführung mit CLI-Parameter-Unterstützung. + Parse CLI-Argumente und generiere tree.txt. + """ + # CLI-Parser konfigurieren + parser = argparse.ArgumentParser( + description="Generiert eine ASCII-Baumstruktur eines Verzeichnisses." + ) + # Positionsargument: root_path (optional) + parser.add_argument( + "root_path", + nargs="?", + default=".", + help="Pfad des Stammverzeichnisses (default: aktuelles Verzeichnis)." + ) + # Optionale Flags und Parameter + parser.add_argument( + "-n", "--max-files-per-dir", + type=int, + default=2, + help="Maximale Anzahl Dateien pro Verzeichnis (default: 2)." + ) + parser.add_argument( + "-d", "--max-depth", + type=int, + help="Maximale Rekursionstiefe; unbegrenzt, wenn nicht gesetzt." + ) + parser.add_argument( + "--no-align-comments", + action="store_false", + dest="align_comments", + help="Deaktiviert das Ausrichten der Kommentare am Zeilenende." + ) + + # CLI-Argumente einlesen + args = parser.parse_args() + + # Konfiguration auf Basis der CLI-Argumente erstellen + config = TreeScannerConfig( + root_path=args.root_path, + max_files_per_dir=args.max_files_per_dir, + max_depth=args.max_depth, + align_comments=args.align_comments + ) scanner = TreeScanner(config) tree_output = scanner.generate_tree() + + # Ausgabe in tree.txt schreiben with open("tree.txt", "w", encoding="utf-8") as f: f.write(tree_output + "\n")