Modularisiere Plattform (Phase 1 & 2): RunnerRegistry-Plugin-System, standardisierter Python-Adapter und deklaratives Kurs-Manifest (courses/python/course.json)
This commit is contained in:
parent
108da4c12a
commit
58ef8a3fb4
7 changed files with 443 additions and 211 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
202
assets/runners/python_runner.js
Normal file
202
assets/runners/python_runner.js
Normal file
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
})();
|
||||
52
assets/runners/runner_registry.js
Normal file
52
assets/runners/runner_registry.js
Normal file
|
|
@ -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());
|
||||
}
|
||||
};
|
||||
})();
|
||||
77
courses/python/course.json
Normal file
77
courses/python/course.json
Normal file
|
|
@ -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" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
109
workspace.html
109
workspace.html
|
|
@ -328,47 +328,19 @@
|
|||
<script src="assets/certificate.js"></script>
|
||||
<script src="assets/parsons.js"></script>
|
||||
<script src="assets/code_guard.js"></script>
|
||||
<script src="assets/runners/runner_registry.js"></script>
|
||||
<script src="assets/runners/python_runner.js"></script>
|
||||
<script src="assets/monaco_ide.js"></script>
|
||||
|
||||
<script>
|
||||
// Manifest aller Kapitel für sequentielle Navigation
|
||||
const CHAPTERS_MANIFEST = [
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "01_erste_schritte_taschenrechner", title: "G01. Taschenrechner" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "02_variablen_und_datentypen", title: "G02. Variablen & Typen" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "03_ein_und_ausgabe", title: "G03. Ein- & Ausgabe" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "04_verzweigungen_und_bedingungen", title: "G04. Bedingungen" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "05_schleifen_und_wiederholungen", title: "G05. Schleifen" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "06_funktionen_und_module", title: "G06. Funktionen & Module" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "07_listen_und_sequenzen", title: "G07. Listen" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "08_textverarbeitung_und_strings", title: "G08. Strings" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "09_dictionaries_und_sets", title: "G09. Dicts & Sets" },
|
||||
{ track: "lehrpfad_1_grundlagen", folder: "10_comprehensions_datum_algorithmen", title: "G10. Comprehensions" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "00_fehlersuche_und_grundlagen", title: "OOP 00. Debugging" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "01_einstieg_klassen", title: "OOP 01. Klassen" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "02_init_und_self", title: "OOP 02. Konstruktor" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "03_methoden_und_verhalten", title: "OOP 03. Methoden" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "04_str_und_darstellung", title: "OOP 04. __str__" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "04b_umstieg_vscode", title: "Tooling 04b. VS Code" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "04c_git_und_versionskontrolle", title: "Tooling 04c. Git" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "05_objekte_kombinieren", title: "OOP 05. Komposition" },
|
||||
{ track: "lehrpfad_2_oop_einstieg", folder: "06_abschlussprojekt_tamagotchi", title: "Projekt 06. Tamagotchi" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "07_referenzen_und_speicher", title: "Adv 07. Speicher" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "08_operator_overloading_dunder", title: "Adv 08. Dunder" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "09_eigene_unit_tests_schreiben", title: "Adv 09. TDD Tests" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "10_vererbung_und_super", title: "Adv 10. Vererbung" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "11_polymorphie_und_interfaces", title: "Adv 11. Polymorphie" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "12_exceptions_und_fehlerbehandlung", title: "Adv 12. Exceptions" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "13_persistenz_json_und_csv", title: "Adv 13. Persistenz" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "14_gui_mit_tkinter", title: "Adv 14. Tkinter" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "15_parameter_und_container", title: "Adv 15. Container" },
|
||||
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "16_master_abschlussprojekt", title: "Master 16. Abschlussprojekt" },
|
||||
{ track: "lehrpfad_4_professional_data_engineering", folder: "17_regulaere_ausdruecke_re", title: "Pro 17. Regex & Text Mining" },
|
||||
{ track: "lehrpfad_4_professional_data_engineering", folder: "18_funktionale_programmierung_lambda", title: "Pro 18. Functional & Lambda" },
|
||||
{ track: "lehrpfad_4_professional_data_engineering", folder: "19_generatoren_und_itertools", title: "Pro 19. Generatoren & Itertools" },
|
||||
{ track: "lehrpfad_4_professional_data_engineering", folder: "20_dataclasses_und_typehints", title: "Pro 20. Dataclasses & Decorators" },
|
||||
{ track: "lehrpfad_4_professional_data_engineering", folder: "21_datenbanken_und_sqlite", title: "Pro 21. SQLite & Relationale DBs" },
|
||||
{ track: "lehrpfad_4_professional_data_engineering", folder: "22_web_apis_und_json_feeds", title: "Pro 22. REST APIs & JSON Feeds" }
|
||||
];
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const courseId = urlParams.get("course") || "python";
|
||||
const trackFolder = urlParams.get("track") || "lehrpfad_1_grundlagen";
|
||||
const chapterFolder = urlParams.get("chapter") || "01_erste_schritte_taschenrechner";
|
||||
const chapterId = `${trackFolder}/${chapterFolder}`;
|
||||
|
||||
let CHAPTERS_MANIFEST = [];
|
||||
let currentCourseManifest = null;
|
||||
|
||||
function switchTab(tabId) {
|
||||
document.querySelectorAll(".pane-tab-btn").forEach(b => b.classList.remove("active"));
|
||||
|
|
@ -385,22 +357,45 @@
|
|||
if (el) el.style.display = el.style.display === "block" ? "none" : "block";
|
||||
}
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const trackFolder = urlParams.get("track") || "lehrpfad_1_grundlagen";
|
||||
const chapterFolder = urlParams.get("chapter") || "01_erste_schritte_taschenrechner";
|
||||
const chapterId = `${trackFolder}/${chapterFolder}`;
|
||||
|
||||
// Nächstes Kapitel ermitteln
|
||||
const currentIndex = CHAPTERS_MANIFEST.findIndex(c => c.folder === chapterFolder);
|
||||
const nextChapter = (currentIndex >= 0 && currentIndex < CHAPTERS_MANIFEST.length - 1) ? CHAPTERS_MANIFEST[currentIndex + 1] : null;
|
||||
|
||||
document.getElementById("btn-next-chapter")?.addEventListener("click", () => {
|
||||
if (nextChapter) {
|
||||
window.location.href = `workspace.html?track=${nextChapter.track}&chapter=${nextChapter.folder}`;
|
||||
} else {
|
||||
window.location.href = "index.html#zertifikate";
|
||||
async function loadCourseManifest() {
|
||||
try {
|
||||
const res = await fetch(`courses/${courseId}/course.json`);
|
||||
if (res.ok) {
|
||||
currentCourseManifest = await res.json();
|
||||
const list = [];
|
||||
(currentCourseManifest.tracks || []).forEach(tr => {
|
||||
(tr.chapters || []).forEach(ch => {
|
||||
list.push({
|
||||
track: tr.id,
|
||||
folder: ch.folder,
|
||||
title: ch.title,
|
||||
taskFile: ch.taskFile || "aufgabe.py",
|
||||
testFile: ch.testFile || "test_aufgabe.py"
|
||||
});
|
||||
});
|
||||
});
|
||||
CHAPTERS_MANIFEST = list;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Konnte course.json nicht laden, nutze Fallback-Manifest:", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function setupNextChapterButton() {
|
||||
const currentIndex = CHAPTERS_MANIFEST.findIndex(c => c.folder === chapterFolder);
|
||||
const nextChapter = (currentIndex >= 0 && currentIndex < CHAPTERS_MANIFEST.length - 1) ? CHAPTERS_MANIFEST[currentIndex + 1] : null;
|
||||
|
||||
const nextBtn = document.getElementById("btn-next-chapter");
|
||||
if (nextBtn) {
|
||||
nextBtn.onclick = () => {
|
||||
if (nextChapter) {
|
||||
window.location.href = `workspace.html?course=${courseId}&track=${nextChapter.track}&chapter=${nextChapter.folder}`;
|
||||
} else {
|
||||
window.location.href = "index.html#zertifikate";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWorkspaceData() {
|
||||
try {
|
||||
|
|
@ -488,13 +483,17 @@
|
|||
document.getElementById("hints-modal-overlay").style.display = "flex";
|
||||
});
|
||||
|
||||
// 5. Monaco IDE initialisieren
|
||||
await loadCourseManifest();
|
||||
setupNextChapterButton();
|
||||
|
||||
// 5. Monaco IDE initialisieren mit modularer Sprache
|
||||
const lang = currentCourseManifest ? (currentCourseManifest.language || "python") : "python";
|
||||
window.initWorkspace({
|
||||
chapterId: chapterId,
|
||||
starterCode: aufgabeCode,
|
||||
testCode: testCode,
|
||||
solutionCode: loesungCode
|
||||
});
|
||||
}, lang);
|
||||
|
||||
} catch (err) {
|
||||
document.getElementById("tab-content-theorie").innerHTML = `<div class="box box-danger">Fehler beim Laden: ${err.message}</div>`;
|
||||
|
|
|
|||
Loading…
Reference in a new issue