munchboka-edutools 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of munchboka-edutools might be problematic. Click here for more details.
- munchboka_edutools/__init__.py +184 -0
- munchboka_edutools/_plotmath_shim.py +126 -0
- munchboka_edutools/_version.py +2 -0
- munchboka_edutools/directives/__init__.py +1 -0
- munchboka_edutools/directives/admonitions.py +389 -0
- munchboka_edutools/directives/cas_popup.py +428 -0
- munchboka_edutools/directives/clear.py +103 -0
- munchboka_edutools/directives/dialogue.py +137 -0
- munchboka_edutools/directives/escape_room.py +296 -0
- munchboka_edutools/directives/escape_room2.py +318 -0
- munchboka_edutools/directives/factor_tree.py +552 -0
- munchboka_edutools/directives/flashcards.py +233 -0
- munchboka_edutools/directives/ggb.py +209 -0
- munchboka_edutools/directives/ggb_icon.py +105 -0
- munchboka_edutools/directives/ggb_popup.py +308 -0
- munchboka_edutools/directives/horner.py +326 -0
- munchboka_edutools/directives/interactive_code.py +75 -0
- munchboka_edutools/directives/jeopardy.py +252 -0
- munchboka_edutools/directives/jeopardy2.py +636 -0
- munchboka_edutools/directives/multi_plot.py +2524 -0
- munchboka_edutools/directives/multi_plot2.py +252 -0
- munchboka_edutools/directives/pair_puzzle.py +191 -0
- munchboka_edutools/directives/parsons.py +109 -0
- munchboka_edutools/directives/plot.py +3758 -0
- munchboka_edutools/directives/poly_icon.py +111 -0
- munchboka_edutools/directives/polydiv.py +346 -0
- munchboka_edutools/directives/popup.py +245 -0
- munchboka_edutools/directives/quiz.py +291 -0
- munchboka_edutools/directives/quiz2.py +453 -0
- munchboka_edutools/directives/signchart.py +519 -0
- munchboka_edutools/directives/signchart2.py +1545 -0
- munchboka_edutools/directives/timed_quiz.py +436 -0
- munchboka_edutools/directives/turtle.py +157 -0
- munchboka_edutools/static/css/admonitions.css +2012 -0
- munchboka_edutools/static/css/cas_popup.css +242 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_cm.css +112 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_default_cm.css +40 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_high_contrast_cm.css +141 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_cm.css +120 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_default_cm.css +108 -0
- munchboka_edutools/static/css/code_mirror_themes/one_dark_cm.css +121 -0
- munchboka_edutools/static/css/dialogue.css +92 -0
- munchboka_edutools/static/css/escapeRoom/escape-room.css +223 -0
- munchboka_edutools/static/css/figures.css +321 -0
- munchboka_edutools/static/css/flashcards.css +219 -0
- munchboka_edutools/static/css/general_style.css +74 -0
- munchboka_edutools/static/css/github-dark-high-contrast.css +141 -0
- munchboka_edutools/static/css/github-dark.css +147 -0
- munchboka_edutools/static/css/github-light.css +155 -0
- munchboka_edutools/static/css/interactive_code/style.css +575 -0
- munchboka_edutools/static/css/interactive_code.css +582 -0
- munchboka_edutools/static/css/jeopardy.css +553 -0
- munchboka_edutools/static/css/pairPuzzle/style.css +177 -0
- munchboka_edutools/static/css/parsons/parsonsPuzzle.css +331 -0
- munchboka_edutools/static/css/popup.css +115 -0
- munchboka_edutools/static/css/quiz.css +377 -0
- munchboka_edutools/static/css/timedQuiz.css +375 -0
- munchboka_edutools/static/icons/ggb/mode_evaluate.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_extremum.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_intersect.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_nsolve.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_numeric.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_point.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_solve.svg +1 -0
- munchboka_edutools/static/icons/misc/windows-logo.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicdown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicup.svg +3 -0
- munchboka_edutools/static/icons/polyicons/frown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/smile.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/stickers/edit.svg +1 -0
- munchboka_edutools/static/icons/stickers/pencil_square.svg +3 -0
- munchboka_edutools/static/js/casThemeManager.js +99 -0
- munchboka_edutools/static/js/escapeRoom/escape-room.js +219 -0
- munchboka_edutools/static/js/flashcards.js +199 -0
- munchboka_edutools/static/js/geogebra-setup.js +6 -0
- munchboka_edutools/static/js/highlight-init.js +6 -0
- munchboka_edutools/static/js/interactiveCode/codeEditor.js +648 -0
- munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +441 -0
- munchboka_edutools/static/js/interactiveCode/pythonRunner.js +336 -0
- munchboka_edutools/static/js/interactiveCode/turtleCode.js +203 -0
- munchboka_edutools/static/js/interactiveCode/workerManager.js +353 -0
- munchboka_edutools/static/js/jeopardy.js +560 -0
- munchboka_edutools/static/js/pairPuzzle/draggableItem.js +64 -0
- munchboka_edutools/static/js/pairPuzzle/dropZone.js +119 -0
- munchboka_edutools/static/js/pairPuzzle/game.js +160 -0
- munchboka_edutools/static/js/parsons/parsonsPuzzle.js +641 -0
- munchboka_edutools/static/js/popup.js +85 -0
- munchboka_edutools/static/js/quiz.js +566 -0
- munchboka_edutools/static/js/skulpt/skulpt.js +35721 -0
- munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js +184 -0
- munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js +244 -0
- munchboka_edutools/static/js/timedQuiz/utils.js +6 -0
- munchboka_edutools/static/js/utils.js +3 -0
- munchboka_edutools-0.2.3.dist-info/METADATA +109 -0
- munchboka_edutools-0.2.3.dist-info/RECORD +157 -0
- munchboka_edutools-0.2.3.dist-info/WHEEL +4 -0
- munchboka_edutools-0.2.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// pythonRunner.js
|
|
2
|
+
|
|
3
|
+
class PythonRunner {
|
|
4
|
+
constructor(outputId, errorBoxId, preloadPackages = ['casify']) {
|
|
5
|
+
this.outputId = outputId; // ID of the HTML element where output will be displayed
|
|
6
|
+
this.errorBoxId = errorBoxId; // ID of the HTML element for displaying errors
|
|
7
|
+
this.workerManager = WorkerManager.getInstance(preloadPackages);
|
|
8
|
+
this.preloadPackages = preloadPackages;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Prepares and runs the code provided in the editor.
|
|
13
|
+
* @param {Object} editor - The CodeMirror editor instance containing the code.
|
|
14
|
+
*/
|
|
15
|
+
async run(editor, outputId = null) {
|
|
16
|
+
// Wait until worker is ready and preload packages are loaded
|
|
17
|
+
try {
|
|
18
|
+
await this.workerManager.workerReadyPromise;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("Worker failed to initialize:", error);
|
|
21
|
+
this.handleErrorMessage("Failed to initialize Python environment.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.editorInstance = editor;
|
|
26
|
+
this.editorInstance.clearLineHighlights();
|
|
27
|
+
let code = editor.getValue();
|
|
28
|
+
this.currentCode = code;
|
|
29
|
+
|
|
30
|
+
if (outputId) {
|
|
31
|
+
this.outputId = outputId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle input statements and modify the code accordingly
|
|
35
|
+
const inputStatements = this.findInputStatements(this.currentCode);
|
|
36
|
+
if (inputStatements.length > 0) {
|
|
37
|
+
const userValues = await this.getUserInputs(inputStatements);
|
|
38
|
+
this.currentCode = this.replaceInputStatements(this.currentCode, userValues);
|
|
39
|
+
console.log("Modified code:", this.currentCode);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Extract and load necessary packages
|
|
43
|
+
const packages = this.extractPackageNames(this.currentCode);
|
|
44
|
+
|
|
45
|
+
if (!packages.includes('matplotlib')) {
|
|
46
|
+
packages.push('matplotlib');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log("Packages to load:", packages);
|
|
50
|
+
if (packages.length > 0) {
|
|
51
|
+
try {
|
|
52
|
+
await this.workerManager.loadPackages(packages);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Handle package load error
|
|
55
|
+
console.error("Failed to load packages:", error);
|
|
56
|
+
this.handleErrorMessage(error.message);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
const callback = (data) => {
|
|
63
|
+
if (data.type === 'stdout') {
|
|
64
|
+
this.handleWorkerMessage(data);
|
|
65
|
+
} else if (data.type === 'stderr') {
|
|
66
|
+
this.handleErrorMessage(data.msg); // Displays the error
|
|
67
|
+
} else if (data.type === 'plot') {
|
|
68
|
+
const outputElement = document.getElementById(this.outputId);
|
|
69
|
+
if (!outputElement) {
|
|
70
|
+
console.error("Output element not found:", this.outputId);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const img = document.createElement('img');
|
|
75
|
+
img.src = 'data:image/png;base64,' + data.data;
|
|
76
|
+
img.style.width = '100%'; // Or any other appropriate styling
|
|
77
|
+
img.style.height = 'auto'; // Or any other appropriate styling
|
|
78
|
+
img.style.maxHeight = '500px'; // Example: set a max width
|
|
79
|
+
|
|
80
|
+
outputElement.appendChild(img);
|
|
81
|
+
this.scrollToBottom(outputElement); // Scroll to show new plot
|
|
82
|
+
} else if (data.type === 'executionComplete') {
|
|
83
|
+
console.log("Code execution complete for messageId:", data.messageId);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
this.workerManager.runCode(this.currentCode, callback);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Handles incoming messages from the WorkerManager.
|
|
94
|
+
* @param {Object} data - The parsed message data from the worker.
|
|
95
|
+
*/
|
|
96
|
+
handleWorkerMessage(data) {
|
|
97
|
+
const { type, msg } = data;
|
|
98
|
+
console.log("Message from worker:", data);
|
|
99
|
+
const outputElement = document.getElementById(this.outputId);
|
|
100
|
+
|
|
101
|
+
if (!outputElement) {
|
|
102
|
+
console.error("Output element not found:", this.outputId);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (type === 'stdout') {
|
|
107
|
+
// Replace "&" with "∧", "oo" with "∞", and "|" with "∨"
|
|
108
|
+
let formattedMsg = msg
|
|
109
|
+
.replace(/And\(([^)]+)\)/g, (match, p1) => {
|
|
110
|
+
// Split conditions by comma, trim spaces, and wrap each in parentheses
|
|
111
|
+
const conditions = p1.split(',').map(cond => cond.trim());
|
|
112
|
+
return conditions.map(cond => `(${cond})`).join(' ∧ ');
|
|
113
|
+
})
|
|
114
|
+
// .replace(/&/g, '∧')
|
|
115
|
+
.replace(/oo/g, '∞')
|
|
116
|
+
.replace(/\|/g, '∨');
|
|
117
|
+
outputElement.innerHTML += this.formatErrorMessage(formattedMsg);
|
|
118
|
+
this.highlightLine(this.editorInstance, data.msg);
|
|
119
|
+
this.scrollToBottom(outputElement);
|
|
120
|
+
|
|
121
|
+
} else if (type === 'stderr') {
|
|
122
|
+
console.log("Error message:", msg);
|
|
123
|
+
this.handleErrorMessage(msg);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
scrollToBottom(element) {
|
|
128
|
+
element.scrollTop = element.scrollHeight;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Handles error messages from the worker.
|
|
133
|
+
* @param {string} msg - The error message.
|
|
134
|
+
*/
|
|
135
|
+
handleErrorMessage(msg) {
|
|
136
|
+
const errorElement = document.getElementById(this.errorBoxId);
|
|
137
|
+
if (errorElement) {
|
|
138
|
+
errorElement.innerHTML = this.formatErrorMessage(msg);
|
|
139
|
+
}
|
|
140
|
+
this.highlightLine(this.editorInstance, msg);
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Formats output messages for display.
|
|
146
|
+
* @param {string} msg - The message to be formatted.
|
|
147
|
+
* @returns {string} - The formatted message.
|
|
148
|
+
*/
|
|
149
|
+
formatOutput(msg) {
|
|
150
|
+
return msg;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
highlightLine(editor, msg) {
|
|
155
|
+
const linePattern = /File "<exec>", line (\d+)/;
|
|
156
|
+
const match = linePattern.exec(msg);
|
|
157
|
+
if (match) {
|
|
158
|
+
const lineNumber = parseInt(match[1]) - 1;
|
|
159
|
+
console.log("Highlighting line:", lineNumber);
|
|
160
|
+
editor.highlightLine(lineNumber);
|
|
161
|
+
console.log("Highlighting error at line:", lineNumber);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
formatErrorMessage(errorMsg) {
|
|
166
|
+
let formattedMessage = errorMsg;
|
|
167
|
+
let content = '';
|
|
168
|
+
let title = '';
|
|
169
|
+
let knownError = false;
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// Highlight the line number in the pattern 'File "<exec>", line <number>'
|
|
173
|
+
const fileLinePattern = /File "<exec>", line (\d+)/g;
|
|
174
|
+
formattedMessage = formattedMessage.replace(fileLinePattern, (match, p1) => {
|
|
175
|
+
return match.replace(`line ${p1}`, `<span class="error-line">line ${p1}</span>`);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// Highlight the error type
|
|
180
|
+
const errorTypeMatch = errorMsg.match(/(\w+Error):/);
|
|
181
|
+
if (errorTypeMatch) {
|
|
182
|
+
console.log("Error type match:", errorTypeMatch[1]);
|
|
183
|
+
formattedMessage = formattedMessage.replace(errorTypeMatch[1], `<span class="error-type">${errorTypeMatch[1]}</span>`);
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
return formattedMessage;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Extracts package names from the code based on import statements.
|
|
195
|
+
* @param {string} code - The Python code to be analyzed.
|
|
196
|
+
* @returns {Array<string>} - An array of package names.
|
|
197
|
+
*/
|
|
198
|
+
extractPackageNames(code) {
|
|
199
|
+
// Matches "import <package> as <alias>" and "import <package>"
|
|
200
|
+
const importRegex = /^\s*import\s+([^;\s]+)\s*/gm;
|
|
201
|
+
|
|
202
|
+
// Matches "from <package> import <something>"
|
|
203
|
+
const fromImportRegex = /^\s*from\s+([^;\s]+)\s+import/gm;
|
|
204
|
+
|
|
205
|
+
const packages = new Set();
|
|
206
|
+
const standardLibs = new Set([
|
|
207
|
+
'abc', 'aifc', 'argparse', 'array', 'ast', 'asynchat', 'asyncio', 'asyncore', 'atexit', 'audioop', 'base64',
|
|
208
|
+
'bdb', 'binascii', 'binhex', 'bisect', 'builtins', 'bz2', 'cProfile', 'calendar', 'cgi', 'cgitb', 'chunk',
|
|
209
|
+
'cmath', 'cmd', 'code', 'codecs', 'codeop', 'collections', 'colorsys', 'compileall', 'concurrent', 'configparser',
|
|
210
|
+
'contextlib', 'copy', 'copyreg', 'crypt', 'csv', 'ctypes', 'curses', 'dataclasses', 'datetime', 'dbm', 'decimal',
|
|
211
|
+
'difflib', 'dis', 'distutils', 'doctest', 'email', 'encodings', 'ensurepip', 'enum', 'errno', 'faulthandler',
|
|
212
|
+
'fcntl', 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fractions', 'ftplib', 'functools', 'gc', 'getopt',
|
|
213
|
+
'getpass', 'gettext', 'glob', 'grp', 'gzip', 'hashlib', 'heapq', 'hmac', 'html', 'http', 'imaplib', 'imghdr',
|
|
214
|
+
'imp', 'importlib', 'inspect', 'io', 'ipaddress', 'itertools', 'json', 'keyword', 'lib2to3', 'linecache', 'locale',
|
|
215
|
+
'logging', 'lzma', 'mailbox', 'mailcap', 'marshal', 'math', 'mimetypes', 'mmap', 'modulefinder', 'msilib', 'msvcrt',
|
|
216
|
+
'multiprocessing', 'netrc', 'nntplib', 'numbers', 'operator', 'optparse', 'os', 'ossaudiodev', 'parser', 'pathlib',
|
|
217
|
+
'pdb', 'pickle', 'pickletools', 'pipes', 'pkgutil', 'platform', 'plistlib', 'poplib', 'posix', 'pprint', 'profile',
|
|
218
|
+
'pstats', 'pty', 'pwd', 'py_compile', 'pyclbr', 'pydoc', 'queue', 'quopri', 'random', 're', 'readline', 'reprlib',
|
|
219
|
+
'resource', 'rlcompleter', 'runpy', 'sched', 'secrets', 'select', 'selectors', 'shelve', 'shlex', 'shutil', 'signal',
|
|
220
|
+
'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', 'spwd', 'sqlite3', 'ssl', 'stat', 'statistics',
|
|
221
|
+
'string', 'stringprep', 'struct', 'subprocess', 'sunau', 'symbol', 'symtable', 'sys', 'sysconfig', 'tabnanny',
|
|
222
|
+
'tarfile', 'telnetlib', 'tempfile', 'termios', 'test', 'textwrap', 'threading', 'time', 'timeit', 'tkinter', 'token',
|
|
223
|
+
'tokenize', 'trace', 'traceback', 'tracemalloc', 'tty', 'turtle', 'turtledemo', 'types', 'typing', 'unicodedata',
|
|
224
|
+
'unittest', 'urllib', 'uu', 'uuid', 'venv', 'warnings', 'wave', 'weakref', 'webbrowser', 'winreg', 'winsound',
|
|
225
|
+
'wsgiref', 'xdrlib', 'xml', 'xmlrpc', 'zipapp', 'zipfile', 'zipimport', 'zlib'
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
let match;
|
|
229
|
+
|
|
230
|
+
// Process "import" statements
|
|
231
|
+
while ((match = importRegex.exec(code)) !== null) {
|
|
232
|
+
const packageName = match[1].split('.')[0];
|
|
233
|
+
if (!standardLibs.has(packageName)) {
|
|
234
|
+
packages.add(packageName);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Process "from ... import" statements
|
|
239
|
+
while ((match = fromImportRegex.exec(code)) !== null) {
|
|
240
|
+
const packageName = match[1].split('.')[0];
|
|
241
|
+
if (!standardLibs.has(packageName)) {
|
|
242
|
+
packages.add(packageName);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.log("Packages to load:", Array.from(packages));
|
|
247
|
+
|
|
248
|
+
return Array.from(packages);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Finds input statements in the code.
|
|
253
|
+
* @param {string} code - The Python code to be analyzed.
|
|
254
|
+
* @returns {Array<Object>} - An array of input statements found.
|
|
255
|
+
*/
|
|
256
|
+
findInputStatements(code) {
|
|
257
|
+
const inputRegex = /(\w+)\s*=\s*(int|float|eval)?\(?input\(["'](.*?)["']\)\)?/g;
|
|
258
|
+
let match;
|
|
259
|
+
let inputs = [];
|
|
260
|
+
|
|
261
|
+
while ((match = inputRegex.exec(code)) !== null) {
|
|
262
|
+
inputs.push({
|
|
263
|
+
variable: match[1],
|
|
264
|
+
promptText: match[3]
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return inputs;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Prompts the user for input when input statements are found in the code.
|
|
273
|
+
* @param {Array<Object>} inputs - An array of input statements.
|
|
274
|
+
* @returns {Object} - A dictionary of user inputs mapped to variable names.
|
|
275
|
+
*/
|
|
276
|
+
async getUserInputs(inputs) {
|
|
277
|
+
let userValues = {};
|
|
278
|
+
|
|
279
|
+
for (let input of inputs) {
|
|
280
|
+
let promptText = input.promptText.replace(/['"]+/g, '');
|
|
281
|
+
let userValue = await this.promptUser(promptText);
|
|
282
|
+
userValues[input.variable] = userValue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return userValues;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Prompts the user for input.
|
|
290
|
+
* @param {string} promptText - The text to display in the prompt.
|
|
291
|
+
* @returns {Promise<string>} - A promise that resolves with the user input.
|
|
292
|
+
*/
|
|
293
|
+
promptUser(promptText) {
|
|
294
|
+
return new Promise((resolve) => {
|
|
295
|
+
let userInput = prompt(promptText);
|
|
296
|
+
resolve(userInput);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Replaces input statements in the code with user-provided values.
|
|
302
|
+
* @param {string} code - The Python code.
|
|
303
|
+
* @param {Object} userValues - A dictionary of user-provided values.
|
|
304
|
+
* @returns {string} - The modified code with input statements replaced.
|
|
305
|
+
*/
|
|
306
|
+
|
|
307
|
+
replaceInputStatements(code, userValues) {
|
|
308
|
+
let codeLines = code.split('\n');
|
|
309
|
+
|
|
310
|
+
codeLines = codeLines.map(line => {
|
|
311
|
+
for (let variable in userValues) {
|
|
312
|
+
console.log("Variable: ", variable);
|
|
313
|
+
// Adjust the regex to handle Unicode characters in variable names
|
|
314
|
+
const inputRegex = new RegExp(`\\b${variable}\\b\\s*=\\s*(float|int|eval)?\\(?input\\(.*?\\)\\)?`, 'gu');
|
|
315
|
+
|
|
316
|
+
if (inputRegex.test(line)) {
|
|
317
|
+
let userValue = userValues[variable];
|
|
318
|
+
|
|
319
|
+
// Detect the type of the user input
|
|
320
|
+
if (!isNaN(userValue)) {
|
|
321
|
+
// Check if the input is a number (int or float)
|
|
322
|
+
//userValue = Number(userValue); // Convert the string to a number
|
|
323
|
+
} else if (typeof userValue === 'string') {
|
|
324
|
+
// Check if the input is a string and doesn't contain only numbers
|
|
325
|
+
userValue = `"${userValue}"`; // Add quotes around string inputs
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
line = line.replace(inputRegex, `${variable} = ${userValue}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return line;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return codeLines.join('\n');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
function generateUUID() {
|
|
2
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
3
|
+
const r = (Math.random() * 16) | 0;
|
|
4
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
5
|
+
return v.toString(16);
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TurtleCode {
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} containerId - ID of an existing <div> that will hold this environment.
|
|
13
|
+
* @param {string} [initialCode=""] - Initial Python code to display in the editor.
|
|
14
|
+
* @param {Object} [cmOptions={}] - Additional CodeMirror options to merge in.
|
|
15
|
+
*/
|
|
16
|
+
constructor(containerId, initialCode = "", cmOptions = {}) {
|
|
17
|
+
this.containerId = containerId;
|
|
18
|
+
this.container = document.getElementById(containerId);
|
|
19
|
+
if (!this.container) {
|
|
20
|
+
throw new Error(`Container with id="${containerId}" not found.`);
|
|
21
|
+
}
|
|
22
|
+
this.initialCode = initialCode;
|
|
23
|
+
this.cmOptions = cmOptions;
|
|
24
|
+
|
|
25
|
+
this.uniqueSuffix = generateUUID(); // to avoid ID collisions
|
|
26
|
+
|
|
27
|
+
this.createUI();
|
|
28
|
+
|
|
29
|
+
// Initialize your existing CodeEditor on the created <textarea>
|
|
30
|
+
this.editor = new CodeEditor(this.textAreaEl.id);
|
|
31
|
+
|
|
32
|
+
// Add a small delay before setting the initial code
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
this.editor.setValue(this.initialCode);
|
|
35
|
+
}, 300); // 100ms delay should be enough
|
|
36
|
+
// this.editor.setValue(this.initialCode);
|
|
37
|
+
|
|
38
|
+
this.runButtonEl.addEventListener("click", () => this.runCode());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
createUI() {
|
|
42
|
+
// Clear any existing content in the container
|
|
43
|
+
this.container.innerHTML = "";
|
|
44
|
+
|
|
45
|
+
// A flex container to hold the editor (left) and the turtle div (right)
|
|
46
|
+
const editorAndCanvasContainer = document.createElement("div");
|
|
47
|
+
|
|
48
|
+
editorAndCanvasContainer.classList.add("turtle-env"); // for styling
|
|
49
|
+
|
|
50
|
+
editorAndCanvasContainer.style.display = "flex";
|
|
51
|
+
editorAndCanvasContainer.style.flexWrap = "wrap"; // in case of narrow screens
|
|
52
|
+
editorAndCanvasContainer.style.width = "100%";
|
|
53
|
+
|
|
54
|
+
// --- LEFT COLUMN: Editor, Run Button, Output ---
|
|
55
|
+
const editorContainer = document.createElement("div");
|
|
56
|
+
|
|
57
|
+
editorContainer.classList.add("turtle-left"); // for styling
|
|
58
|
+
|
|
59
|
+
editorContainer.style.flex = "1"; // Take 50% of available width
|
|
60
|
+
editorContainer.style.minWidth = "100px"; // Reasonable min width
|
|
61
|
+
editorContainer.style.display = "flex";
|
|
62
|
+
editorContainer.style.flexDirection = "column";
|
|
63
|
+
|
|
64
|
+
// Textarea for CodeMirror
|
|
65
|
+
this.textAreaEl = document.createElement("textarea");
|
|
66
|
+
this.textAreaEl.id = `skulpt-editor-${this.uniqueSuffix}`;
|
|
67
|
+
// Hide the raw <textarea> so CodeMirror can replace it
|
|
68
|
+
this.textAreaEl.style.display = "none";
|
|
69
|
+
editorContainer.appendChild(this.textAreaEl);
|
|
70
|
+
|
|
71
|
+
// Run Button
|
|
72
|
+
this.runButtonEl = document.createElement("button");
|
|
73
|
+
this.runButtonEl.className = "button button-run";
|
|
74
|
+
this.runButtonEl.id = `skulpt-run-btn-${this.uniqueSuffix}`;
|
|
75
|
+
this.runButtonEl.textContent = "Kjør kode";
|
|
76
|
+
this.runButtonEl.style.margin = "1em 0";
|
|
77
|
+
editorContainer.appendChild(this.runButtonEl);
|
|
78
|
+
|
|
79
|
+
// Output <pre>
|
|
80
|
+
this.outputEl = document.createElement("pre");
|
|
81
|
+
this.outputEl.className = "pythonoutput";
|
|
82
|
+
this.outputEl.id = `skulpt-output-${this.uniqueSuffix}`;
|
|
83
|
+
this.outputEl.style.border = "1px solid #ccc";
|
|
84
|
+
this.outputEl.style.padding = "8px";
|
|
85
|
+
this.outputEl.style.minHeight = "2em";
|
|
86
|
+
this.outputEl.style.whiteSpace = "pre-wrap";
|
|
87
|
+
editorContainer.appendChild(this.outputEl);
|
|
88
|
+
|
|
89
|
+
editorAndCanvasContainer.appendChild(editorContainer);
|
|
90
|
+
|
|
91
|
+
// --- RIGHT COLUMN: Turtle Div ---
|
|
92
|
+
const canvasContainer = document.createElement("div");
|
|
93
|
+
|
|
94
|
+
// canvasContainer.classList.add("turtle-right"); // for styling
|
|
95
|
+
|
|
96
|
+
canvasContainer.style.flex = "1"; // Take the other 50%
|
|
97
|
+
canvasContainer.style.minWidth = "350px"; // Reasonable min width
|
|
98
|
+
canvasContainer.style.display = "flex";
|
|
99
|
+
canvasContainer.style.flexDirection = "column";
|
|
100
|
+
canvasContainer.style.alignItems = "center";
|
|
101
|
+
canvasContainer.style.justifyContent = "center";
|
|
102
|
+
|
|
103
|
+
// Turtle div
|
|
104
|
+
this.canvasEl = document.createElement("div");
|
|
105
|
+
this.canvasEl.id = `skulpt-canvas-${this.uniqueSuffix}`;
|
|
106
|
+
this.canvasEl.style.border = "1px solid #ccc";
|
|
107
|
+
this.canvasEl.style.width = "95%"; // Adjust as needed
|
|
108
|
+
this.canvasEl.style.height = "400px";
|
|
109
|
+
this.canvasEl.style.marginTop = "0em";
|
|
110
|
+
this.canvasEl.style.boxSizing = "border-box";
|
|
111
|
+
canvasContainer.appendChild(this.canvasEl);
|
|
112
|
+
|
|
113
|
+
editorAndCanvasContainer.appendChild(canvasContainer);
|
|
114
|
+
|
|
115
|
+
// Finally, append the flex container into the main container
|
|
116
|
+
this.container.appendChild(editorAndCanvasContainer);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
runCode() {
|
|
120
|
+
let userCode = this.editor.getValue();
|
|
121
|
+
this.outputEl.innerHTML = ""; // clear output
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
const mode = document.documentElement.getAttribute("data-mode");
|
|
125
|
+
// default to black
|
|
126
|
+
let forcedColor = "black";
|
|
127
|
+
if (mode === "dark") {
|
|
128
|
+
forcedColor = "white";
|
|
129
|
+
}
|
|
130
|
+
else if (mode === "light") {
|
|
131
|
+
forcedColor = "black";
|
|
132
|
+
}
|
|
133
|
+
else if (mode === "auto") {
|
|
134
|
+
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
135
|
+
forcedColor = prefersDarkScheme ? "white" : "black";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log("Mode", mode);
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
// Safely prepend a snippet that ensures turtle is white
|
|
142
|
+
// We also do a basic check to ensure turtle is imported before coloring
|
|
143
|
+
// (some users might not have "import turtle" at all).
|
|
144
|
+
// "import turtle" will just re-import gracefully if the user already had it.
|
|
145
|
+
const snippet = `
|
|
146
|
+
try:
|
|
147
|
+
import turtle
|
|
148
|
+
turtle.color("${forcedColor}")
|
|
149
|
+
screen = turtle.Screen()
|
|
150
|
+
|
|
151
|
+
# screen.setup(width=300, height=300)
|
|
152
|
+
|
|
153
|
+
# Set a coordinate system so the center of the visible area is (0,0).
|
|
154
|
+
# For example, left = -200, right = 200, bottom = -150, top = 150
|
|
155
|
+
# screen.setworldcoordinates(-200, -150, 200, 150)
|
|
156
|
+
except:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
`;
|
|
160
|
+
userCode = snippet + userCode;
|
|
161
|
+
|
|
162
|
+
// Output function for Python's print
|
|
163
|
+
const outf = (text) => {
|
|
164
|
+
this.outputEl.innerHTML += text;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Helper for Skulpt to read built-in libs
|
|
168
|
+
const builtinRead = (filename) => {
|
|
169
|
+
if (!Sk.builtinFiles || !Sk.builtinFiles["files"][filename]) {
|
|
170
|
+
throw new Error("File not found: " + filename);
|
|
171
|
+
}
|
|
172
|
+
return Sk.builtinFiles["files"][filename];
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Configure Skulpt
|
|
176
|
+
Sk.pre = this.outputEl.id;
|
|
177
|
+
Sk.configure({
|
|
178
|
+
output: outf,
|
|
179
|
+
read: builtinRead,
|
|
180
|
+
python3: true,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Point turtle to our <div>
|
|
184
|
+
(Sk.TurtleGraphics || (Sk.TurtleGraphics = {})).target = this.canvasEl.id;
|
|
185
|
+
|
|
186
|
+
// Asynchronously run the code
|
|
187
|
+
Sk.misceval.asyncToPromise(() => {
|
|
188
|
+
return Sk.importMainWithBody("<stdin>", false, userCode, true);
|
|
189
|
+
}).then(
|
|
190
|
+
() => {
|
|
191
|
+
// success
|
|
192
|
+
},
|
|
193
|
+
(err) => {
|
|
194
|
+
// error
|
|
195
|
+
this.outputEl.innerHTML = err.toString();
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function makeTurtleCode(containerId, initialCode = "", cmOptions = {}) {
|
|
202
|
+
return new TurtleCode(containerId, initialCode, cmOptions);
|
|
203
|
+
}
|