From 58ef8a3fb45a341fbe1c8f77401eab0bb64b15d4 Mon Sep 17 00:00:00 2001 From: Alex Mueller Date: Sun, 30 Aug 2026 00:30:49 +0200 Subject: [PATCH] Modularisiere Plattform (Phase 1 & 2): RunnerRegistry-Plugin-System, standardisierter Python-Adapter und deklaratives Kurs-Manifest (courses/python/course.json) --- .gamification.json | 2 +- assets/gamification_data.js | 2 +- assets/monaco_ide.js | 210 ++++++++---------------------- assets/runners/python_runner.js | 202 ++++++++++++++++++++++++++++ assets/runners/runner_registry.js | 52 ++++++++ courses/python/course.json | 77 +++++++++++ workspace.html | 109 ++++++++-------- 7 files changed, 443 insertions(+), 211 deletions(-) create mode 100644 assets/runners/python_runner.js create mode 100644 assets/runners/runner_registry.js create mode 100644 courses/python/course.json 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 @@ + +