commit 86456cafa321edba7ab144c05eaa80c0f846f783 Author: Adam Skotarczak Date: Sat Apr 26 03:02:58 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fce5316 --- /dev/null +++ b/.gitignore @@ -0,0 +1,133 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.env.* +!.env.example +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Custom: +logs/ +log/ +release/ +#media/ +*.zip +localstorage/ +.vscode/ +*.txt +*.ini +TODO.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9613e48 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# CHANGELOG + +- 2025-04-25 - initial commit + + - **Geändert:** + - [x] `treescanner.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] `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. + + - **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/LICENSE b/LICENSE new file mode 100644 index 0000000..1989462 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +MIT License with Attribution Requirement + +Copyright (c) 2025 Adam Skotarczak + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, 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**: Any public use or distribution of this Software, +modified or unmodified, must include a clear and visible attribution to the original author: + +**Adam Skotarczak ** + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d24e6d8 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# TreeScanner + +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 +``` + +## 🔧 Verwendung als Standalone + +```bash +python treescanner.py +``` + +Erzeugt eine Datei `tree.txt` mit der Verzeichnisstruktur ab dem aktuellen Pfad. + +## 🧩 Verwendung als Modul + +```python +from treescanner import TreeScanner, TreeScannerConfig + +config = TreeScannerConfig(root_path=".", max_depth=2) +scanner = TreeScanner(config) +output = scanner.generate_tree() +print(output) +``` + +## ⚙️ Konfiguration + +Die `TreeScannerConfig`-Klasse erlaubt dir u. a.: + +- `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 + +## 📄 Lizenz + +MIT (optional anpassen) diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/__main__.py b/__main__.py new file mode 100644 index 0000000..e69de29 diff --git a/config.py b/config.py new file mode 100644 index 0000000..539487f --- /dev/null +++ b/config.py @@ -0,0 +1,21 @@ + +# config.py +# Beispielconfig: +# +# from .config import TreeScannerConfig +# + +class TreeScannerConfig: + def __init__(self, + root_path=".", + folder_icon="📁", + file_icon="📄", + max_files_per_dir=2, + max_depth=None, + align_comments=True): + self.root_path = root_path + self.folder_icon = folder_icon + self.file_icon = file_icon + self.max_files_per_dir = max_files_per_dir + self.max_depth = max_depth + self.align_comments = align_comments diff --git a/media/favicon.ico b/media/favicon.ico new file mode 100644 index 0000000..784682d Binary files /dev/null and b/media/favicon.ico differ diff --git a/media/logo-bw-1024x1024.png b/media/logo-bw-1024x1024.png new file mode 100644 index 0000000..0bb779d Binary files /dev/null and b/media/logo-bw-1024x1024.png differ diff --git a/media/logo-bw-alpha-512x512.png b/media/logo-bw-alpha-512x512.png new file mode 100644 index 0000000..07a8b7c Binary files /dev/null and b/media/logo-bw-alpha-512x512.png differ diff --git a/media/logo-colour-1024x1024.png b/media/logo-colour-1024x1024.png new file mode 100644 index 0000000..b3f5ded Binary files /dev/null and b/media/logo-colour-1024x1024.png differ diff --git a/media/logo-colour-alpha-512x512.png b/media/logo-colour-alpha-512x512.png new file mode 100644 index 0000000..f31d0f3 Binary files /dev/null and b/media/logo-colour-alpha-512x512.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4e0ff7c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "treescanner" +version = "0.1.0" +description = "Ein Verzeichnisscanner als CLI-Tool und Python-Modul" +authors = [ + { name="Adam Skotarczak", email="adam@skotarczak.net" } +] +readme = "README.md" +requires-python = ">=3.7" +license = { text = "MIT" } +keywords = ["filesystem", "tree", "scanner", "cli", "modul"] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/scanner.py b/scanner.py new file mode 100644 index 0000000..93a58b9 --- /dev/null +++ b/scanner.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +""" +Verzeichnisscanner mit strukturierter Ausgabe. +Funktioniert sowohl als Standalone-Skript als auch als einbindbares Modul. +""" + +import os +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): # + self.root_path = root_path + self.folder_icon = folder_icon + self.file_icon = file_icon + self.max_files_per_dir = max_files_per_dir + self.max_depth = max_depth + self.align_comments = align_comments + +# === Hauptklasse === +class TreeScanner: + def __init__(self, config: TreeScannerConfig): + self.config = config + + def scan_directory(self, path: str, depth: int = 0, prefix: str = "") -> List[str]: + lines = [] + try: + entries = sorted(os.listdir(path)) + except PermissionError: + return [f"{prefix}└── [Zugriff verweigert] {path}"] + + 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))] + + for idx, folder in enumerate(folders): + folder_path = os.path.join(path, folder) + connector = "├── " if idx < len(folders) - 1 or files else "└── " + line = f"{prefix}{connector}{self.config.folder_icon} {folder}" + lines.append(line) + if self.config.max_depth is None or depth < self.config.max_depth: + 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) + + if len(files) > self.config.max_files_per_dir: + remaining = len(files) - self.config.max_files_per_dir + lines.append(f"{prefix}└── ") + + return lines + + def align_lines_with_comments(self, lines: List[str]) -> List[str]: + max_length = max(len(line.rstrip()) for line in lines) + return [ + line.rstrip() + (" " * (max_length - len(line.rstrip()) + 2)) + "# " + for line in lines + ] + + def generate_tree(self) -> str: + 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}/"] + lines += self.scan_directory(self.config.root_path) + + if self.config.align_comments: + lines = self.align_lines_with_comments(lines) + + return "\n".join(lines) + +# === Standalone-Ausführung === +def main(): + config = TreeScannerConfig() + scanner = TreeScanner(config) + tree_output = scanner.generate_tree() + with open("tree.txt", "w", encoding="utf-8") as f: + f.write(tree_output + "\n") + +if __name__ == "__main__": + main() diff --git a/test_usage.py b/test_usage.py new file mode 100644 index 0000000..be41771 --- /dev/null +++ b/test_usage.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +from scanner import TreeScanner, TreeScannerConfig + +# Beispielkonfiguration +config = TreeScannerConfig( + root_path=".", + max_depth=2, + align_comments=True +) + +scanner = TreeScanner(config) +baum = scanner.generate_tree() +print(baum)