diff --git a/.gamification.json b/.gamification.json
index 59b3275..8c64ae6 100644
--- a/.gamification.json
+++ b/.gamification.json
@@ -13,5 +13,5 @@
"streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29",
"historie": [],
- "zuletzt_aktualisiert": "2026-08-29T23:32:59.778016"
+ "zuletzt_aktualisiert": "2026-08-30T00:30:42.090205"
}
\ No newline at end of file
diff --git a/assets/gamification_data.js b/assets/gamification_data.js
index c95bb89..f642fc2 100644
--- a/assets/gamification_data.js
+++ b/assets/gamification_data.js
@@ -14,7 +14,7 @@ window.GAMIFICATION_DATA = {
"streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29",
"historie": [],
- "zuletzt_aktualisiert": "2026-08-29T23:32:59.778016"
+ "zuletzt_aktualisiert": "2026-08-30T00:30:42.090205"
};
window.GAMIFICATION_BADGES_KATALOG = {
"rechenkonegin": {
diff --git a/assets/monaco_ide.js b/assets/monaco_ide.js
index 681104a..f7d3120 100644
--- a/assets/monaco_ide.js
+++ b/assets/monaco_ide.js
@@ -1,20 +1,17 @@
/**
- * ⚡ GEHÄRTETE MONACO WEB-IDE & PYODIDE RUNNER ENGINE ⚡
- * =====================================================
- * Interaktive Entwicklungsumgebung mit:
- * - Integriertem CODE_GUARD Sicherheits-Filter (Schadcode- & Exploit-Sperre)
- * - Monaco Editor (VS Code Engine mit Autocomplete & Syntax-Highlighting)
+ * ⚡ UNIVERSAL MODULAR MONACO WEB-IDE ⚡
+ * =======================================
+ * Sprachunabhängige Entwicklungsumgebung mit:
+ * - Dynamischer Monaco Editor Initialisierung (Python, JavaScript, SQL, Rust etc.)
+ * - Delegierung an pluggbare Language Runner via RunnerRegistry
* - Tastatur-Shortcuts: Strg+Enter (Run), Strg+Shift+Enter (Test), Strg+S (Save)
- * - In-Browser Pyodide WebAssembly Python Runner (95% Client-Execution)
- * - Endlosschleifen-Timeout-Schutz (5000ms)
- * - Reaktivität mit Fehler-Dolmetscher & Gamification
+ * - Reaktives Terminal, Auto-Save & Gamification Integration
*/
(function () {
let monacoEditor = null;
- let pyodideInstance = null;
- let pyodideLoading = false;
let currentChapterData = null;
+ let currentLanguage = "python";
let autoSaveTimeout = null;
function escapeHtml(text) {
@@ -27,13 +24,22 @@
.replace(/'/g, "'");
}
- // 1. Initialisiere Monaco Editor via CDN
- function initMonacoEditor(initialCode = "") {
+ function getActiveRunner() {
+ if (window.RunnerRegistry) {
+ const runner = window.RunnerRegistry.get(currentLanguage);
+ if (runner) return runner;
+ }
+ return null;
+ }
+
+ // 1. Initialisiere Monaco Editor
+ function initMonacoEditor(initialCode = "", langId = "python") {
+ currentLanguage = langId;
const editorContainer = document.getElementById("monaco-editor-container");
if (!editorContainer) return;
if (window.monaco) {
- createMonacoInstance(initialCode);
+ createMonacoInstance(initialCode, langId);
return;
}
@@ -44,7 +50,7 @@
paths: { vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs" }
});
window.require(["vs/editor/editor.main"], () => {
- createMonacoInstance(initialCode);
+ createMonacoInstance(initialCode, langId);
});
};
loaderScript.onerror = () => {
@@ -53,14 +59,17 @@
document.head.appendChild(loaderScript);
}
- function createMonacoInstance(initialCode) {
+ function createMonacoInstance(initialCode, langId) {
const editorContainer = document.getElementById("monaco-editor-container");
if (!editorContainer) return;
editorContainer.innerHTML = "";
+ const runner = getActiveRunner();
+ const monacoLang = runner ? runner.monacoLanguage : langId;
+
monacoEditor = window.monaco.editor.create(editorContainer, {
value: initialCode,
- language: "python",
+ language: monacoLang,
theme: "vs-dark",
fontSize: 14,
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace",
@@ -95,88 +104,29 @@
}
});
- // 2. Pyodide WebAssembly Initialisierung mit Sandbox-Bootstrap
- async function getPyodide() {
- if (pyodideInstance) return pyodideInstance;
- if (pyodideLoading) {
- while (pyodideLoading) {
- await new Promise((r) => setTimeout(r, 100));
- }
- return pyodideInstance;
- }
-
- pyodideLoading = true;
- logToTerminal("⏳ Initialisiere geschützte Python Sandbox Engine... Bitte kurz warten.\n", "info");
-
- try {
- if (!window.loadPyodide) {
- await new Promise((resolve, reject) => {
- const script = document.createElement("script");
- script.src = "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js";
- script.onload = resolve;
- script.onerror = reject;
- document.head.appendChild(script);
- });
- }
-
- pyodideInstance = await window.loadPyodide({
- stdout: (text) => logToTerminal(text + "\n", "stdout"),
- stderr: (text) => logToTerminal(text + "\n", "stderr")
- });
-
- // Sandbox-Härtung im Pyodide-Interpreter aktivieren
- if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
- await pyodideInstance.runPythonAsync(window.CODE_GUARD.getSandboxBootstrap());
- }
-
- logToTerminal("✅ Geschützte Python WebAssembly Sandbox bereitgestellt!\n\n", "success");
- } catch (err) {
- logToTerminal(`❌ Fehler beim Laden der Sandbox: ${err.message}\n`, "error");
- } finally {
- pyodideLoading = false;
- }
- return pyodideInstance;
- }
-
- // 3. Code-Ausführung mit Sicherheitsprüfung & Timeout
+ // 2. Code-Ausführung via aktivem Language-Runner
async function runCode() {
const code = getEditorCode();
clearTerminal();
- // Layer 1: Code-Guard Sicherheits-Prüfung
- if (window.CODE_GUARD) {
- const check = window.CODE_GUARD.validate(code);
- if (!check.safe) {
- logToTerminal(`\n${check.error}\n\n`, "error");
- return;
- }
+ const runner = getActiveRunner();
+ if (!runner) {
+ logToTerminal(`❌ Kein Language-Runner für '${currentLanguage}' registriert.\n`, "error");
+ return;
}
- logToTerminal("▶ Starte Python-Skript (Strg+Enter)...\n----------------------------------------\n", "info");
+ logToTerminal(`▶ Starte Ausführung mit ${runner.displayName} (Strg+Enter)...\n----------------------------------------\n`, "info");
- const py = await getPyodide();
- if (!py) return;
+ const result = await runner.runCode(code, logToTerminal);
- try {
- let timeoutHandle;
- const timeoutPromise = new Promise((_, reject) => {
- timeoutHandle = setTimeout(() => {
- reject(new Error("Zeitüberschreitung: Dein Code lief länger als 5 Sekunden (Endlosschleife vermutet)."));
- }, 5000);
- });
-
- const execPromise = py.runPythonAsync(code);
- await Promise.race([execPromise, timeoutPromise]);
- clearTimeout(timeoutHandle);
-
- logToTerminal("\n----------------------------------------\n✨ Skript erfolgreich ausgeführt!\n", "success");
- } catch (err) {
- logToTerminal(`\n❌ Ausführungsfehler:\n${err.message}\n`, "error");
- suggestErrorInterpreter(err.message);
+ if (result.success) {
+ logToTerminal("\n----------------------------------------\n✨ Erfolgreich ausgeführt!\n", "success");
+ } else if (result.error) {
+ suggestErrorInterpreter(result.error);
}
}
- // 4. Unittest-Ausführung mit Sicherheitsprüfung
+ // 3. Test-Ausführung via aktivem Language-Runner
async function runTests() {
if (!currentChapterData || !currentChapterData.testCode) {
logToTerminal("❌ Keine Unittests für dieses Kapitel gefunden.\n", "error");
@@ -186,78 +136,29 @@
const userCode = getEditorCode();
clearTerminal();
- // Layer 1: Code-Guard Sicherheits-Prüfung
- if (window.CODE_GUARD) {
- const check = window.CODE_GUARD.validate(userCode);
- if (!check.safe) {
- logToTerminal(`\n${check.error}\n\n`, "error");
- return;
- }
+ const runner = getActiveRunner();
+ if (!runner) {
+ logToTerminal(`❌ Kein Test-Runner für '${currentLanguage}' verfügbar.\n`, "error");
+ return;
}
- logToTerminal("🧪 Führe automatisierte Unittests aus (Strg+Shift+Enter)...\n----------------------------------------\n", "info");
+ logToTerminal(`🧪 Führe automatisierte Tests aus (Strg+Shift+Enter)...\n----------------------------------------\n`, "info");
- const py = await getPyodide();
- if (!py) return;
+ const result = await runner.runTests(userCode, currentChapterData.testCode, logToTerminal);
- try {
- py.FS.writeFile("aufgabe.py", userCode);
-
- const testRunnerScript = `
-import unittest, io, sys
-from unittest import TextTestRunner
-
-for mod in list(sys.modules.keys()):
- if mod in ('aufgabe', 'test_aufgabe'):
- del sys.modules[mod]
-
-import aufgabe
-
-${currentChapterData.testCode}
-
-suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
-stream = io.StringIO()
-runner = TextTestRunner(stream=stream, verbosity=2)
-result = runner.run(suite)
-
-output_text = stream.getvalue()
-success = result.wasSuccessful()
-total_runs = result.testsRun
-failures = len(result.failures)
-errors = len(result.errors)
-(success, total_runs, failures, errors, output_text)
-`;
-
- let timeoutHandle;
- const timeoutPromise = new Promise((_, reject) => {
- timeoutHandle = setTimeout(() => {
- reject(new Error("Zeitüberschreitung: Unittest lief länger als 6 Sekunden."));
- }, 6000);
- });
-
- const [isSuccess, totalRuns, fails, errs, outputText] = await Promise.race([
- py.runPythonAsync(testRunnerScript),
- timeoutPromise
- ]);
- clearTimeout(timeoutHandle);
-
- logToTerminal(outputText + "\n", "stdout");
-
- if (isSuccess) {
- logToTerminal(`\n🎉 HERVORRAGEND! Alle ${totalRuns} Tests erfolgreich bestanden! (+100 XP)\n`, "success");
- triggerSuccessCelebration();
- recordChapterSolved();
- } else {
- logToTerminal(`\n⚠️ ${fails + errs} von ${totalRuns} Tests fehlgeschlagen. Überarbeite deinen Code!\n`, "warning");
- suggestErrorInterpreter(outputText);
+ if (result.success) {
+ logToTerminal(`\n🎉 HERVORRAGEND! Alle ${result.total || 'erforderlichen'} Tests erfolgreich bestanden! (+100 XP)\n`, "success");
+ triggerSuccessCelebration();
+ recordChapterSolved();
+ } else {
+ logToTerminal(`\n⚠️ ${result.failures || 1} Tests fehlgeschlagen. Überarbeite deinen Code!\n`, "warning");
+ if (result.rawOutput) {
+ suggestErrorInterpreter(result.rawOutput);
}
- } catch (err) {
- logToTerminal(`\n❌ Fehler beim Ausführen der Testsuite:\n${err.message}\n`, "error");
- suggestErrorInterpreter(err.message);
}
}
- // 5. Hilfsfunktionen
+ // 4. Hilfsfunktionen
function getEditorCode() {
if (monacoEditor) {
return monacoEditor.getValue();
@@ -378,14 +279,15 @@ errors = len(result.errors)
}
}
- // 6. Init Workspace Controller
- window.initWorkspace = function (chapterData) {
+ // 5. Init Workspace Controller
+ window.initWorkspace = function (chapterData, language = "python") {
currentChapterData = chapterData;
+ currentLanguage = language || "python";
const savedDraft = localStorage.getItem(`code_draft_${chapterData.chapterId}`);
const initialCode = savedDraft || chapterData.starterCode || "";
- initMonacoEditor(initialCode);
+ initMonacoEditor(initialCode, currentLanguage);
document.getElementById("btn-run-code")?.addEventListener("click", runCode);
document.getElementById("btn-run-tests")?.addEventListener("click", runTests);
diff --git a/assets/runners/python_runner.js b/assets/runners/python_runner.js
new file mode 100644
index 0000000..6aee077
--- /dev/null
+++ b/assets/runners/python_runner.js
@@ -0,0 +1,202 @@
+/**
+ * 🐍 PYTHON LANGUAGE RUNNER ADAPTER (Pyodide WebAssembly) 🐍
+ * ==========================================================
+ * Implementiert das standardisierte LanguageRunnerAdapter-Interface:
+ * - Pyodide WASM Engine mit In-Memory Python 3.12
+ * - CODE_GUARD Sicherheits-Prüfung & Sandbox-Bootstrap
+ * - Timeout-Schutz gegen Endlosschleifen
+ * - Unittest-Runner für aufgabe.py & test_aufgabe.py
+ * - Fehler-Dolmetscher Anbindung
+ */
+
+(function () {
+ let pyodideInstance = null;
+ let pyodideLoading = false;
+
+ const PythonAdapter = {
+ id: "python",
+ displayName: "Python 3.12 (Pyodide WebAssembly)",
+ icon: "🐍",
+ monacoLanguage: "python",
+ fileExtension: ".py",
+ testFileExtension: "test_aufgabe.py",
+
+ /**
+ * Lädt und initialisiert die Pyodide WebAssembly Engine
+ */
+ async loadEngine(logCallback) {
+ if (pyodideInstance) return pyodideInstance;
+ if (pyodideLoading) {
+ while (pyodideLoading) {
+ await new Promise((r) => setTimeout(r, 100));
+ }
+ return pyodideInstance;
+ }
+
+ pyodideLoading = true;
+ if (logCallback) logCallback("⏳ Initialisiere Python WebAssembly Engine (Pyodide)... Bitte kurz warten.\n", "info");
+
+ try {
+ if (!window.loadPyodide) {
+ await new Promise((resolve, reject) => {
+ const script = document.createElement("script");
+ script.src = "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js";
+ script.onload = resolve;
+ script.onerror = reject;
+ document.head.appendChild(script);
+ });
+ }
+
+ pyodideInstance = await window.loadPyodide();
+
+ // Sandbox-Härtung im Pyodide-Interpreter aktivieren
+ if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
+ await pyodideInstance.runPythonAsync(window.CODE_GUARD.getSandboxBootstrap());
+ }
+
+ if (logCallback) logCallback("✅ Python WebAssembly Sandbox bereitgestellt!\n\n", "success");
+ } catch (err) {
+ if (logCallback) logCallback(`❌ Fehler beim Laden der Python Sandbox: ${err.message}\n`, "error");
+ throw err;
+ } finally {
+ pyodideLoading = false;
+ }
+ return pyodideInstance;
+ },
+
+ /**
+ * Sicherheitsprüfung vor Code-Ausführung
+ */
+ validateSecurity(code) {
+ if (window.CODE_GUARD && window.CODE_GUARD.validate) {
+ return window.CODE_GUARD.validate(code);
+ }
+ return { safe: true };
+ },
+
+ /**
+ * Führt Python-Code aus (▶ Code ausführen)
+ */
+ async runCode(code, logCallback) {
+ const securityCheck = this.validateSecurity(code);
+ if (!securityCheck.safe) {
+ logCallback(`\n${securityCheck.error}\n\n`, "error");
+ return { success: false, error: securityCheck.error };
+ }
+
+ const py = await this.loadEngine(logCallback);
+ if (!py) return { success: false, error: "Pyodide nicht verfügbar" };
+
+ // Umleitung von stdout & stderr
+ py.setStdout({ batched: (text) => logCallback(text + "\n", "stdout") });
+ py.setStderr({ batched: (text) => logCallback(text + "\n", "stderr") });
+
+ try {
+ let timeoutHandle;
+ const timeoutPromise = new Promise((_, reject) => {
+ timeoutHandle = setTimeout(() => {
+ reject(new Error("Zeitüberschreitung: Dein Code lief länger als 5 Sekunden (Endlosschleife vermutet)."));
+ }, 5000);
+ });
+
+ const execPromise = py.runPythonAsync(code);
+ await Promise.race([execPromise, timeoutPromise]);
+ clearTimeout(timeoutHandle);
+
+ return { success: true };
+ } catch (err) {
+ logCallback(`\n❌ Ausführungsfehler:\n${err.message}\n`, "error");
+ return { success: false, error: err.message };
+ }
+ },
+
+ /**
+ * Führt automatisierte Unittests aus (🧪 Tests starten)
+ */
+ async runTests(userCode, testCode, logCallback) {
+ const securityCheck = this.validateSecurity(userCode);
+ if (!securityCheck.safe) {
+ logCallback(`\n${securityCheck.error}\n\n`, "error");
+ return { success: false, error: securityCheck.error };
+ }
+
+ const py = await this.loadEngine(logCallback);
+ if (!py) return { success: false, error: "Pyodide nicht verfügbar" };
+
+ try {
+ py.FS.writeFile("aufgabe.py", userCode);
+
+ const testRunnerScript = `
+import unittest, io, sys
+from unittest import TextTestRunner
+
+for mod in list(sys.modules.keys()):
+ if mod in ('aufgabe', 'test_aufgabe'):
+ del sys.modules[mod]
+
+import aufgabe
+
+${testCode}
+
+suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+stream = io.StringIO()
+runner = TextTestRunner(stream=stream, verbosity=2)
+result = runner.run(suite)
+
+output_text = stream.getvalue()
+success = result.wasSuccessful()
+total_runs = result.testsRun
+failures = len(result.failures)
+errors = len(result.errors)
+(success, total_runs, failures, errors, output_text)
+`;
+
+ let timeoutHandle;
+ const timeoutPromise = new Promise((_, reject) => {
+ timeoutHandle = setTimeout(() => {
+ reject(new Error("Zeitüberschreitung: Unittest lief länger als 6 Sekunden."));
+ }, 6000);
+ });
+
+ const [isSuccess, totalRuns, fails, errs, outputText] = await Promise.race([
+ py.runPythonAsync(testRunnerScript),
+ timeoutPromise
+ ]);
+ clearTimeout(timeoutHandle);
+
+ logCallback(outputText + "\n", "stdout");
+
+ return {
+ success: Boolean(isSuccess),
+ total: totalRuns,
+ passed: totalRuns - (fails + errs),
+ failures: fails,
+ errors: errs,
+ rawOutput: outputText
+ };
+ } catch (err) {
+ logCallback(`\n❌ Fehler beim Ausführen der Testsuite:\n${err.message}\n`, "error");
+ return { success: false, error: err.message, rawOutput: err.message };
+ }
+ },
+
+ /**
+ * Übersetzt Python-Tracebacks
+ */
+ translateError(traceback) {
+ if (window.INTERPRETER && window.INTERPRETER.translate) {
+ return window.INTERPRETER.translate(traceback);
+ }
+ return null;
+ }
+ };
+
+ // In Registry registrieren
+ if (window.RunnerRegistry) {
+ window.RunnerRegistry.register(PythonAdapter);
+ } else {
+ document.addEventListener("DOMContentLoaded", () => {
+ if (window.RunnerRegistry) window.RunnerRegistry.register(PythonAdapter);
+ });
+ }
+})();
diff --git a/assets/runners/runner_registry.js b/assets/runners/runner_registry.js
new file mode 100644
index 0000000..0a176b9
--- /dev/null
+++ b/assets/runners/runner_registry.js
@@ -0,0 +1,52 @@
+/**
+ * 🔌 UNIVERSAL LANGUAGE RUNNER REGISTRY 🔌
+ * =========================================
+ * Verwaltet austauschbare Language-Runner für polyglotte Code-Ausführung
+ * (Python, JavaScript, SQL, Rust etc.) in der Monaco Web-IDE.
+ */
+
+(function () {
+ const runners = new Map();
+
+ window.RunnerRegistry = {
+ /**
+ * Registriert einen neuen Language Runner Adapter
+ * @param {Object} adapter
+ */
+ register(adapter) {
+ if (!adapter || !adapter.id) {
+ throw new Error("Runner Adapter muss eine gültige 'id' besitzen.");
+ }
+ runners.set(adapter.id.toLowerCase(), adapter);
+ console.log(`🔌 [RunnerRegistry] Runner '${adapter.displayName || adapter.id}' erfolgreich registriert.`);
+ },
+
+ /**
+ * Ruft einen registrierten Runner anhand der Sprach-ID ab
+ * @param {string} languageId
+ * @returns {Object|null}
+ */
+ get(languageId) {
+ if (!languageId) return null;
+ return runners.get(languageId.toLowerCase()) || null;
+ },
+
+ /**
+ * Gibt alle registrierten Sprach-IDs zurück
+ * @returns {string[]}
+ */
+ list() {
+ return Array.from(runners.keys());
+ },
+
+ /**
+ * Prüft, ob ein Runner für eine Sprache verfügbar ist
+ * @param {string} languageId
+ * @returns {boolean}
+ */
+ has(languageId) {
+ if (!languageId) return false;
+ return runners.has(languageId.toLowerCase());
+ }
+ };
+})();
diff --git a/courses/python/course.json b/courses/python/course.json
new file mode 100644
index 0000000..c507c79
--- /dev/null
+++ b/courses/python/course.json
@@ -0,0 +1,77 @@
+{
+ "id": "python",
+ "title": "Python Software Engineering",
+ "language": "python",
+ "icon": "🐍",
+ "runner": "python",
+ "description": "Vom Einsteiger über professionelle Objektorientierung bis hin zu Data Engineering und Desktop-Apps in 33 Modulen.",
+ "tracks": [
+ {
+ "id": "lehrpfad_1_grundlagen",
+ "title": "🌱 Lehrpfad 1: Grundlagen der Programmierung",
+ "description": "Zahlen, Variablen, Bedingungen, Schleifen, Funktionen, Listen, Strings, Dicts & Sets.",
+ "certificateKey": "lehrpfad_1",
+ "chapters": [
+ { "folder": "01_erste_schritte_taschenrechner", "title": "G01: Python als Taschenrechner", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "02_variablen_und_datentypen", "title": "G02: Variablen & Datentypen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "03_ein_und_ausgabe", "title": "G03: Interaktive Ein- & Ausgabe", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "04_verzweigungen_und_bedingungen", "title": "G04: Verzweigungen & Bedingungen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "05_schleifen_und_wiederholungen", "title": "G05: Schleifen & Wiederholungen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "06_funktionen_und_module", "title": "G06: Eigene Funktionen & Module", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "07_listen_und_sequenzen", "title": "G07: Listen & Sequenzen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "08_textverarbeitung_und_strings", "title": "G08: Textverarbeitung & Strings", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "09_dictionaries_und_sets", "title": "G09: Dictionaries & Sets", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "10_comprehensions_datum_algorithmen", "title": "G10: Comprehensions & Algorithmen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" }
+ ]
+ },
+ {
+ "id": "lehrpfad_2_oop_einstieg",
+ "title": "🏗️ Lehrpfad 2: Einstieg in die Objektorientierung (OOP)",
+ "description": "Klassen, Konstruktor, self, Methoden, Tools (VS Code & Git) und Tamagotchi-Projekt.",
+ "certificateKey": "lehrpfad_2",
+ "chapters": [
+ { "folder": "00_fehlersuche_und_grundlagen", "title": "OOP 00: Python-Fehlersuche", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "01_einstieg_klassen", "title": "OOP 01: Erste Klassen & Instanzen", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "02_init_und_self", "title": "OOP 02: Konstruktor __init__ & self", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "03_methoden_und_verhalten", "title": "OOP 03: Methoden & Verhalten", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "04_str_und_darstellung", "title": "OOP 04: __str__ & String-Darstellung", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "04b_umstieg_vscode", "title": "Tooling 04b: Umstieg auf VS Code", "taskFile": "vscode_test.py", "testFile": "" },
+ { "folder": "04c_git_und_versionskontrolle", "title": "Tooling 04c: Git & Versionskontrolle", "taskFile": "git_uebung.py", "testFile": "" },
+ { "folder": "05_objekte_kombinieren", "title": "OOP 05: Objekte kombinieren", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "06_abschlussprojekt_tamagotchi", "title": "Projekt 06: Virtuelles Haustier (Tamagotchi)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" }
+ ]
+ },
+ {
+ "id": "lehrpfad_3_fortgeschrittenes_oop",
+ "title": "🚀 Lehrpfad 3: Fortgeschrittenes OOP & GUIs",
+ "description": "Speicher-Referenzen, Dunder-Methoden, TDD, Vererbung, Polymorphie, Exceptions, JSON/CSV, Tkinter & Masterprojekt.",
+ "certificateKey": "lehrpfad_3",
+ "chapters": [
+ { "folder": "07_referenzen_und_speicher", "title": "Adv 07: Referenzen & Speicher", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "08_operator_overloading_dunder", "title": "Adv 08: Operator Overloading & Dunder", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "09_eigene_unit_tests_schreiben", "title": "Adv 09: Eigene Unit Tests & TDD", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "10_vererbung_und_super", "title": "Adv 10: Vererbung & super()", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "11_polymorphie_und_interfaces", "title": "Adv 11: Polymorphie & Interfaces", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "12_exceptions_und_fehlerbehandlung", "title": "Adv 12: Exceptions & Fehlerbehandlung", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "13_persistenz_json_und_csv", "title": "Adv 13: Datei-Persistenz (JSON & CSV)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "14_gui_mit_tkinter", "title": "Adv 14: Desktop-GUIs mit Tkinter", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "15_parameter_und_container", "title": "Adv 15: Parameter & Eigene Container", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "16_master_abschlussprojekt", "title": "Master 16: Master-Abschlussprojekt", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" }
+ ]
+ },
+ {
+ "id": "lehrpfad_4_professional_data_engineering",
+ "title": "🌟 Lehrpfad 4: Python Professional & Data Engineering",
+ "description": "Reguläre Ausdrücke (re), funktionale Idiome, Generatoren, Dataclasses, Type Hints, SQLite & REST-APIs.",
+ "certificateKey": "lehrpfad_4",
+ "chapters": [
+ { "folder": "17_regulaere_ausdruecke_re", "title": "Pro 17: Reguläre Ausdrücke (re)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "18_funktionale_programmierung_lambda", "title": "Pro 18: Funktionale Programmierung (lambda)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "19_generatoren_und_itertools", "title": "Pro 19: Generatoren & itertools (yield)", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "20_dataclasses_und_typehints", "title": "Pro 20: Dataclasses & Type Hints", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "21_datenbanken_und_sqlite", "title": "Pro 21: Relationale Datenbanken & SQLite", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" },
+ { "folder": "22_web_apis_und_json_feeds", "title": "Pro 22: Web-APIs & JSON-Feeds", "taskFile": "aufgabe.py", "testFile": "test_aufgabe.py" }
+ ]
+ }
+ ]
+}
diff --git a/workspace.html b/workspace.html
index 0e1f266..6bb5ad4 100644
--- a/workspace.html
+++ b/workspace.html
@@ -328,47 +328,19 @@
+
+