diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..b58b603
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/MatrixMania.iml b/.idea/MatrixMania.iml
new file mode 100644
index 0000000..533429d
--- /dev/null
+++ b/.idea/MatrixMania.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..3b0fcb1
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..b79339d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..55c2805
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/install_dependencies.py b/install_dependencies.py
new file mode 100644
index 0000000..5a20e39
--- /dev/null
+++ b/install_dependencies.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+"""Installiert die Abhängigkeiten aus pyproject.toml in die aktuelle Python-Umgebung.
+
+Ersatz für "Install requirements" aus PyCharm, wenn das Projekt eine pyproject.toml
+statt einer requirements.txt hat. Kein Build, keine besondere Projektstruktur nötig,
+nur Standardbibliothek (Python >= 3.11).
+
+Benutzung:
+ In PyCharm: Rechtsklick auf diese Datei -> Run 'install_dependencies'
+ Im Terminal (venv aktiv): python install_dependencies.py
+
+Ohne Parameter wird ALLES installiert:
+ [project].dependencies + alle [project.optional-dependencies] + alle [dependency-groups]
+
+Abweichendes Verhalten:
+ python install_dependencies.py --extra dev nur Basis + Extra "dev"
+ python install_dependencies.py --group test nur Basis + Gruppe "test"
+ python install_dependencies.py --no-main --group dev
+ python install_dependencies.py --list nur anzeigen, nichts installieren
+ python install_dependencies.py --write requirements.txt schreiben statt installieren
+ python install_dependencies.py --write -o reqs.txt
+"""
+from __future__ import annotations
+
+import sys
+
+if sys.version_info < (3, 11):
+ sys.exit("Dieses Skript braucht Python 3.11 oder neuer (tomllib). "
+ f"Aktuell: {sys.version.split()[0]}")
+
+import argparse
+import subprocess
+import tempfile
+import tomllib
+from pathlib import Path
+
+PROJECT_DIR = Path(__file__).resolve().parent
+
+
+def resolve_group(groups: dict, name: str, seen: set[str] | None = None) -> list[str]:
+ """Löst eine Dependency-Group inkl. {include-group = "..."} rekursiv auf."""
+ seen = set() if seen is None else seen
+ if name in seen:
+ raise ValueError(f"Zyklische include-group: {name}")
+ if name not in groups:
+ raise KeyError(f"Dependency-Group '{name}' nicht gefunden")
+ seen.add(name)
+ result: list[str] = []
+ for entry in groups[name]:
+ if isinstance(entry, str):
+ result.append(entry)
+ elif isinstance(entry, dict) and "include-group" in entry:
+ result.extend(resolve_group(groups, entry["include-group"], seen))
+ return result
+
+
+def collect(data: dict, extras: list[str], groups: list[str], no_main: bool) -> list[str]:
+ project = data.get("project", {})
+ optional = project.get("optional-dependencies", {})
+ dep_groups = data.get("dependency-groups", {})
+ deps: list[str] = []
+
+ if not no_main:
+ deps.extend(project.get("dependencies", []))
+ for extra in extras:
+ if extra not in optional:
+ raise KeyError(f"Optional-Dependency '{extra}' nicht gefunden")
+ deps.extend(optional[extra])
+ for group in groups:
+ deps.extend(resolve_group(dep_groups, group))
+
+ # Duplikate entfernen, Reihenfolge behalten
+ return list(dict.fromkeys(d.strip() for d in deps if d.strip()))
+
+
+def main() -> int:
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ p.add_argument("-p", "--pyproject", type=Path, default=PROJECT_DIR / "pyproject.toml",
+ help="Pfad zur pyproject.toml (Standard: neben diesem Skript)")
+ p.add_argument("--extra", action="append", default=None,
+ help="nur diese optional-dependencies (mehrfach möglich)")
+ p.add_argument("--group", action="append", default=None,
+ help="nur diese dependency-groups (mehrfach möglich)")
+ p.add_argument("--no-main", action="store_true", help="[project].dependencies weglassen")
+ p.add_argument("--list", action="store_true", help="Abhängigkeiten nur anzeigen")
+ p.add_argument("--write", action="store_true", help="requirements-Datei schreiben statt installieren")
+ p.add_argument("-o", "--output", type=Path, default=PROJECT_DIR / "requirements.txt",
+ help="Zieldatei für --write")
+ args = p.parse_args()
+
+ if not args.pyproject.exists():
+ print(f"{args.pyproject} nicht gefunden", file=sys.stderr)
+ return 1
+ data = tomllib.loads(args.pyproject.read_text(encoding="utf-8"))
+ all_extras = list(data.get("project", {}).get("optional-dependencies", {}))
+ all_groups = list(data.get("dependency-groups", {}))
+
+ # Ohne --extra/--group: alles. Mit einem davon: nur das Gewählte.
+ selective = args.extra is not None or args.group is not None
+ extras = (args.extra or []) if selective else all_extras
+ groups = (args.group or []) if selective else all_groups
+
+ try:
+ deps = collect(data, extras, groups, args.no_main)
+ except (KeyError, ValueError) as e:
+ print(f"Fehler: {e.args[0]}", file=sys.stderr)
+ print(f" Vorhandene --extra: {', '.join(all_extras) or '(keine)'}", file=sys.stderr)
+ print(f" Vorhandene --group: {', '.join(all_groups) or '(keine)'}", file=sys.stderr)
+ return 1
+
+ parts = ([] if args.no_main else ["Basis"]) + [f"extra:{e}" for e in extras] + [f"group:{g}" for g in groups]
+ print(f"Auswahl: {', '.join(parts) or '(nichts)'}")
+
+ if args.list:
+ print("\n".join(deps) or "(keine Abhängigkeiten)")
+ return 0
+
+ content = f"# Automatisch erzeugt aus {args.pyproject.name} – nicht von Hand bearbeiten\n" + "\n".join(deps) + "\n"
+
+ if args.write:
+ args.output.write_text(content, encoding="utf-8")
+ print(f"{len(deps)} Abhängigkeiten -> {args.output}")
+ return 0
+
+ if not deps:
+ print("Keine Abhängigkeiten gefunden – nichts zu tun.")
+ return 0
+
+ if sys.prefix == sys.base_prefix:
+ print("WARNUNG: Es ist kein virtuelles Environment aktiv – es wird in die globale "
+ f"Python-Installation installiert:\n {sys.prefix}", file=sys.stderr)
+ if sys.stdin.isatty() and input("Trotzdem fortfahren? [j/N] ").strip().lower() not in ("j", "ja", "y", "yes"):
+ return 1
+
+ with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False, encoding="utf-8") as tmp:
+ tmp.write(content)
+ tmp_path = Path(tmp.name)
+ try:
+ print(f"Installiere {len(deps)} Abhängigkeiten in {sys.prefix}\n")
+ return subprocess.call([sys.executable, "-m", "pip", "install", "-r", str(tmp_path)])
+ finally:
+ tmp_path.unlink(missing_ok=True)
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..6d49d24
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[project]
+name = "matrixmania"
+version = "0.1.0"
+dependencies = ["tabulate", "ruff"]
+[project.optional-dependencies]
+dev = ["pytest", "ruff", "pytest-cov"]
\ No newline at end of file
diff --git a/pyproject.toml.py b/pyproject.toml.py
deleted file mode 100644
index 3a776b1..0000000
--- a/pyproject.toml.py
+++ /dev/null
@@ -1,4 +0,0 @@
-[project]
-name = "matrixmania"
-version = "0.1.0"
-dependencies = ["tabulate", "ruff"]
\ No newline at end of file